This is not as general as a monad though, it's just feeding the output of one function into the next.
I'm sure you know this, but the thing that makes monads a more general solution is that (because bind depends on a value produced at runtime) one can dynamically alter control flow, i.e:
fetch = do
user <- fetchUser "url"
if userEmpty user
then do
fetchUserDetails "details"
fetch -- recurse
else
fetchAddress user
For that to work, your functions inside the `composeAsync` array would need to able to return nested promises and at that point you've just reimplemented the continuation monad :)