loading...

Awaiting for async promises in JavaScript

rhymes profile image rhymes ・2 min read

This post originated as a comment to @warrend 's post Clarifying Async and Promises and I decided to repost it as a separate article because it might be useful to others.

I was exploring the other day the possibilities beyond the "then/catch" pattern with promises because to me it still looks like callbacks, neater and cleaner but there has to be a better way, for readability's sake.

In Python land (see the Twisted framework which influenced Node.js) this problem has been already met. In Twisted promises are called "deferreds" but the issue is the same: cascading callbacks, error handlers, callbacks to your error handlers, sibling callbacks and error handlers can still become a mess to read and understand:

.then(() => {}).then(() => {}).then(() => {}).catch((err) => {})
Enter fullscreen mode Exit fullscreen mode

or in Twisted

.addCallback(function).addCallback(function).addCallback(function).addErrback(errHandler)
Enter fullscreen mode Exit fullscreen mode

What they came up with is:

a decorator named inlineCallbacks which allows you to work with Deferreds without writing callback functions.

So in Twisted you can do this:

@inlineCallbacks
def getUsers(self):
    try:
        responseBody = yield makeRequest("GET", "/users")
    except ConnectionError:
       log.failure("makeRequest failed due to connection error")
       return []

   return json.loads(responseBody)
Enter fullscreen mode Exit fullscreen mode

makeRequest returns a deferred (a promise) and this way instead of attaching callbacks and error handlers to it you can wait for the response to come back and if an error arises you handle it there and then with try...except (try/catch in JS). In the latest Python versions you can even do this:

async def bar():
    baz = await someOtherDeferredFunction()
    fooResult = await foo()
    return baz + fooResult
Enter fullscreen mode Exit fullscreen mode

So you can basically use await for the deferred/promises to resolve and write synchronous-looking code instead of attaching callbacks, which brings me back to JavaScript and async/await (same keywords of Python, don't know which came first :D).

Instead of attaching callbacks and error handler to your promise you can use async/await to write more readable code:

async function bar() {
  const a = await someFunction();
  const b = await someOtherFunction();
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

I found this video by Wes Bos very informing:

Discussion

pic
Editor guide
Collapse
ben profile image
Ben Halpern

This post originated as a comment to @warrend's post Clarifying Async and Promises and I decided to repost it as a separate article because it might be useful to others.

I think this is a great pattern to follow. There is so much wisdom in the comments and elevating to post is a good call.

Collapse
rhymes profile image
rhymes Author

Thanks Ben!

Collapse
kingdaro profile image
kingdaro

Some advice: if multiple operations don't depend on the result of others, and especially if they might take a while, doing them concurrently is ideal.

async function bar() {
  const [a, b] = await Promise.all([
    someFunction(),
    someOtherFunction(),
  ])
  return a + b
}

This is a pitfall a lot of people tend to miss when using async/await; thought I should mention it here. Additional info for Promise.all

Collapse
rhymes profile image
rhymes Author

Thank you Darius!

It's worth noting that if any of the promises rejects the entire Promise.all() will be rejected, which is fine if you have to sum two numbers BUT it might not be the case if you are building a crawler (for example).

Maybe one's intention is "fire off these 10 operations and tell me which ones failed and which ones didn't" which I don't think you can do with the ES6 API easily.

Twisted has DeferredList which allows the user to fire off N deferred/promises and then attach a callback to the aggregate so you can iterate on the results and check which one succeeded and which one failed.

Collapse
kingdaro profile image
kingdaro

Good point, didn't think about that. 👍