loading...
Cover image for An async/await gotcha

An async/await gotcha

tyrw profile image Tyler Warnock ・1 min read

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 pretty smooth. Fewer lines of code, more expressive, etc, etc.

But we did have one line of code that was checking to see if a passed variable was a promise or not:

if (promise instanceof Promise) ...

Turns out this fails for async... sometimes.

In node 8, it gives false:

(async () => {})() instanceof Promise
// -> false

In chrome, it gives true:

(async () => {})() instanceof Promise
// -> true

My assumption is that true is the correct response, and that this is a bug to be fixed in node (probably already).

In the meantime, we're using

if (promise instanceof Promise || promise.constructor.name === 'Promise') ...

And I'm still

Discussion

pic
Editor guide
Collapse
tyrw profile image
Tyler Warnock Author

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!

Collapse
cubiclebuddha profile image
Cubicle Buddha

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.

Collapse
tyrw profile image
Tyler Warnock Author

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!

Collapse
strahinjalak profile image
Strahinja Laktovic

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. :)

Collapse
cubiclebuddha profile image
Cubicle Buddha

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

Collapse
spion profile image
Gorgi Kosev

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...

Collapse
adam_cyclones profile image
Adam Crockett

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

Collapse
tyrw profile image
Tyler Warnock Author

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

Collapse
adam_cyclones profile image
Adam Crockett

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.

Thread Thread
tyrw profile image
Tyler Warnock Author

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.

Thread Thread
adam_cyclones profile image
Adam Crockett

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.

Thread Thread
tyrw profile image
Tyler Warnock Author

How many promises are there in your codebase

Lots of them, maybe 5-10k?

Thread Thread
adam_cyclones profile image
Adam Crockett

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

Collapse
dvddpl profile image
Davide de Paolis

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 :-)

Collapse
tyrw profile image
Tyler Warnock Author

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 😃

Collapse
cwspear profile image
Cameron Spear

I'm not a fan of using "Promise" as the variable name for Bluebird.

github.com/petkaantonov/bluebird/i...

Collapse
tyrw profile image
Tyler Warnock Author

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.