An async/await gotcha

Tyler Warnock on May 01, 2019

async is not quite promises (yet?) We've been moving from promises to async/await in our codebase, and for the most part it's been pre... [Read Full]
markdown guide
 

UPDATE: like many people, we've been using Bluebird as a promise library. Turns out this is a Bluebird issue, not a Node vs Chrome issue. Be advised!

 

Really enjoyed the article. Can you clarify what the use case is for duck typing a variable to see if it’s a promise or not?

Because I thought that you can await a non-async function. So if you’re not sure if a function is async, you can await it anyway.

Side note: you could also check if it has a .then property that is typeof function, but again I’m not sure why you wouldn’t just await it as a precaution.

Btw, I’m not in the habit of awaiting every function, but I would do it if the return type is T | Promise<T> since it has the potential of being a promise.

 

The use case was to create a wrapper for testing purposes, so that we could target parts of the code to wait for in our tests rather than using timeouts etc.

In production, the wrapper does nothing, but we overwrite that behavior in our tests so that we can call await asyncWrapper if needed. I may do an article elaborating on the use case, as it's been a really nice pattern for us.

I'm not sure where the "Duck" line gets drawn in JS, but we were using instanceof Promise because it felt the most direct. Checking for .then and typeof function is even duckier, though as I understand it that's the actual spec!

 

One could always make new Promise and resolve inside the function that should be 'wrapped as async', and mock it as such. I don't event think setTimeout is necessary. I look forward to hearing test case elaboration as well. :)

 

Cool, yea it would be fun to see an article on that testing scenario.

 

From my experience the best way to check for promises is

if (item != null && typeof item.then === 'function')

Of course this is not entirely accurate, however, its what most promise libraries use to "assimilate" foreign promises (often called "thenables" from the library perspective) coming from other libraries. Once the existence of then is confirmed, its generally safe to assume it can take one or two callback functions.

instanceof is unfortunately unreliable in many cases. For example it doesn't work across iframes. Similarly if the class being checked is imported from a node module, the instanceof check may fail if npm or yarn decided to install a separate copy for the library for a sub-dependency. speakingjs.com/es5/ch17.html#cross...

 

I'm sorry but you not moving from promises, your still using promises?

 

We are rewriting as we go. We have about 350,000 lines of code, so it's a process 😃

 

But why? Sorry I don't mean to be the negative rubber duck. Async Await (good idea for greenfield projects) but this is syntactic suger that makes little difference because under the hood it's still a promise.

We're not actively going through the codebase and replacing things, but we are able to eliminate a lot of extra scoping and syntax by using async/await as we make changes or add new features.

For example if you want to use the result of a promise several .then calls later, you have to either nest your promises or use a scope variable. With async/await you don't, so it has a nicer tidiness factor.

Agreed promises if used in a certain way, can cause a form of callback hell. How many promises are there in your codebase. 35000 lines doesn't really explain the size of it. To clarify I have been using async await way back when they dropped in typescript last year. I love them.

How many promises are there in your codebase

Lots of them, maybe 5-10k?

Jeez well if it make your codebase cleaner, I can see why now. Thanks for sharing the post 😀.

 

dunno if it can help but David Walsh wrote about something similar few days ago.
It's not really checking against a variable - or more precisely, the result of a function call - but can be useful to decide if await a function or not (instead of adding a then to the result)
checking the constructor.name of a function returns "AsyncFunction" instead of "Function" if that is indeed an async one :-)

 

That's a nice bit of intel. In our case we wanted to capture both promises and async, as we're still using both. But I'm going to keep this one in my head for sure 😃

 
 

We started back when Promise didn't do much (anything?) on its own. Bluebird's docs still recommend using Promise but I agree with you on this one. Bluebird is much more relevant for current use.

code of conduct - report abuse