Flow: Using promise library with async/await

Created on 2 Aug 2016  Â·  15Comments  Â·  Source: facebook/flow

The following code does not pass flow check.

// @flow
/* global describe, it */
const Promise = require('bluebird')

describe('Flow', () => {
  it('should work for async/await pattern.', async () => {
    const value:number = await Promise.resolve(10)
    console.log(value)
  })
})

And here's how flow complains.

test/flow.spec.js:7
  7:     const value:number = await Promise.resolve(10)
                                    ^^^^^^^^^^^^^^^^^^^ call of method `resolve`
  7:     const value:number = await Promise.resolve(10)
                                    ^^^^^^^^^^^^^^^^^^^ Bluebird$Promise. This type is incompatible with
522: declare function $await<T>(p: Promise<T> | T): T;
                                   ^^^^^^^^^^^^^^ union: type application of identifier `Promise` | type parameter `T` of await. See lib: /private/tmp/flow/flowlib_d8b774b/core.js:522
  Member 1:
  522: declare function $await<T>(p: Promise<T> | T): T;
                                     ^^^^^^^^^^ type application of identifier `Promise`. See lib: /private/tmp/flow/flowlib_d8b774b/core.js:522
  Error:
    7:     const value:number = await Promise.resolve(10)
                                      ^^^^^^^^^^^^^^^^^^^ Bluebird$Promise. This type is incompatible with
  522: declare function $await<T>(p: Promise<T> | T): T;
                                     ^^^^^^^^^^ Promise. See lib: /private/tmp/flow/flowlib_d8b774b/core.js:522
  Member 2:
    7:     const value:number = await Promise.resolve(10)
                                ^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `T` of await
  Error:
    7:     const value:number = await Promise.resolve(10)
                                ^^^^^^^^^^^^^^^^^^^^^^^^^ Bluebird$Promise. This type is incompatible with
    7:     const value:number = await Promise.resolve(10)
                       ^^^^^^ number

Problem here seems like await expects native Promise type, not Bluebird$Promise. How can I use Bluebird$Promise instead of native Promise?

I'm using bluebird interface file with this one.

According to this comment there isn't way to do this yet. In that case I just want to raise this issue.

asynawait bug declarations feature request

Most helpful comment

The problem here is that an async function needs to be able to await any "thenable". Here is an example of what should be accepted by flow:

/* @flow */

const thenable = { then: (cb) => cb('thenable') }

async function foo () {
  const a: string = await Promise.resolve('a')
  const b: string = await Promise.resolve(thenable)
  const c: string = await thenable
  return a + b + c
}

Here's a basic "thenable" being awaited inside an async function on node 7 with the --harmony-async-await flag:

thenable

All 15 comments

Possible workaround for this would be to: (1) redeclare $await (https://github.com/facebook/flow/blob/master/lib/core.js#L522) in a [lib] file, using an import of 'bluebird' Promise, but importing in lib files is not supported; or (2) https://github.com/facebook/flow/issues/1635#issuecomment-208512377

Should the definition of await be using the $Shape of Promise instead of a Promise declaration itself?

It seems that according to spec the only thing that is required is then method

Actually it doesn't even require a then method. The engine always coerces the awaited expression into a promise automatically, so you are allowed to await a value that may or may not be a promise.

The spec defines how await desugars. The internal desugared logic for each awaited expression is: take the expression to the right of await, wrap it in Promise.resolve(...), and then wait for the fulfilment value of _that_ promise.

So these are both totally valid (and are functionally identical):

await Promise.resolve('foo');
await 'foo';

Here is a live example using Babel's desugaring, which follows the spec correctly.

Why would you ever want to await a non-thenable value... Possible reasons:

  1. writing a library function that accepts input as either a promise or just a plain value
  2. you might want to explicitly pause the current function until the next tick, to allow any queued events to fire before the current function continues.

Admittedly these aren't everyday use cases, but they are valid according to the spec, so it would be wrong for Flow to type-check awaited values as thenables.

In conclusion... Flow should allow any value to follow the await keyword, even null/undefined. (The only thing you can say for sure about typing is: the evaluated result of the entire (await X) expression is guaranteed _not_ to be a thenable.)

so it would be wrong for Flow to type-check awaited values as thenables.

No one says that await-ing non-thenable is an error, it's just what's required for a value to be unwrapped.

No one says that await-ing non-thenable is an error, it's just what's required for a value to be unwrapped.

Not sure I follow. Can you elaborate? AFAICT, flow's core.js declares that await requires a thenable. Or am I misunderstanding?

No, it currently requires it to be an instance of global Promise, so it won't work with Bluebird or other libs. This PR fixes that: https://github.com/facebook/flow/pull/2291

Sorry, I meant promise. Anyway... it looks like your PR just changes await to also accept Thenable as well as Promise. But my point was it can be any value at all, even primitives or null/undefined. For example, await 123 is valid. With your PR, doesn't it still fail code like await 123?

With your PR, doesn't it still fail code like await 123?

It doesn't, and it doesn't fail now

OK looks like I misunderstood the error message

Possible workaround for this would be to: (1) redeclare $await (https://github.com/facebook/flow/blob/master/lib/core.js#L522) in a [lib] file, using an import of 'bluebird' Promise, but importing in lib files is not supported; or (2) #1635 (comment)

I've had declare function $await<T>(p: BluebirdPromise<T> | T): T; work just fine in the definition file I'm using for Bluebird.

For me the clearest workaround is to override global Promise in app entry point:
global.Promise = require('bluebird')
And then if you need bluebird specific methods I will import bluebird again :

import Bluebird from 'bluebird'
async function main () {
  Bluebird.delay(100)
}

@barczaG It won't really work, though, because Flow won't see result of Bluebird.delay(100) as Promise

The problem here is that an async function needs to be able to await any "thenable". Here is an example of what should be accepted by flow:

/* @flow */

const thenable = { then: (cb) => cb('thenable') }

async function foo () {
  const a: string = await Promise.resolve('a')
  const b: string = await Promise.resolve(thenable)
  const c: string = await thenable
  return a + b + c
}

Here's a basic "thenable" being awaited inside an async function on node 7 with the --harmony-async-await flag:

thenable

Hey! What's the status of this?

There was a PR fixing this: https://github.com/facebook/flow/pull/2291 — but it seems to have been reverted without explanation, and I can't find any info about the reasons why.

This is blocking me because I want to provide a thenable class in a _library_ — so I can't use the workaround of overriding $await, since it's something all users of the library would have to do.

I can send a PR again, but will be easier if I understand why the original one was rejected.

/cc @mroch @bhosmer

Was this page helpful?
0 / 5 - 0 ratings