Typescript: Use bluebird promise implementation with async/await

Created on 29 Oct 2016  ·  6Comments  ·  Source: microsoft/TypeScript

I use bluebird a lot in my project. It has benefits over standard Promise implementation, such as better api and performance. Alwo bluebird's promises fully backward compatible with standard Promise interface, so, it can be used as replacement of standard Promise.

But, trying to use async / await functions with Bluebird I got an error about impossibility to use Bluebird as return of async function. Consider the example:

async function readFile(fileName: string): Bluebird<string> {
}

As for now I should explicitly write something like that:

function  readFile(fileName: string): Bluebird<string> {
  return Bluebird.try(async () => {

  })
}

This extra wrapper makes me sad :( While I can force to use bluebird's promise implementation by overloading global.Promise or hoook my transpiler (babel or somewhat else), I can't use it with typescript :(

Discussion

Most helpful comment

@Strate Are you using "target": "es5", or "target": "es2015"
In the former case, this works fine:

import Bluebird from 'bluebird';

export async function getString(): Bluebird<string> {
  return await 'hello';
}

export async function useString(): Bluebird<void> {
  console.log(await getString());
}

In the latter case, the above code will issue an error stating

The return type of an async function or method must be the global Promise type.

This is required by the ECMAScript spec which mandates that async function return instances of the global Promise function. You can get around this with by writing the following

// bootstrap.ts
import Bluebird from 'bluebird';
Promise = Promise || Bluebird as any;

// example.ts
export async function getString() {
    return await 'hello';
}

export async function useString() {
    console.log(await getString());
}

But your code will not be spec compliant if you are targeting a runtime that provides Promises.

This has nothing specifically to do with TypeScript.

All 6 comments

Works for me. You're doing something wrong.

@Strate Are you using "target": "es5", or "target": "es2015"
In the former case, this works fine:

import Bluebird from 'bluebird';

export async function getString(): Bluebird<string> {
  return await 'hello';
}

export async function useString(): Bluebird<void> {
  console.log(await getString());
}

In the latter case, the above code will issue an error stating

The return type of an async function or method must be the global Promise type.

This is required by the ECMAScript spec which mandates that async function return instances of the global Promise function. You can get around this with by writing the following

// bootstrap.ts
import Bluebird from 'bluebird';
Promise = Promise || Bluebird as any;

// example.ts
export async function getString() {
    return await 'hello';
}

export async function useString() {
    console.log(await getString());
}

But your code will not be spec compliant if you are targeting a runtime that provides Promises.

This has nothing specifically to do with TypeScript.

Thanks @aluanhaddad for patient explanation! Learn a lot from your comment.

I peruse the TC39's proposal, specifically in the section abstract-async-function-start, where the return behavior is specified, it reads:

If result.[[type]] is normal, then
   Perform ! Call(promiseCapability.[[Resolve]], undefined, «undefined»).

After another lengthy search, the promiseCapability.[[Resolve]] is promise's native resolve, which is defined here

8. Let then be Get(resolution, "then").
9. If then is an abrupt completion, then

    a. Return RejectPromise(promise, then.[[value]]).

10. Let thenAction be then.[[value]].
11. If IsCallable(thenAction) is false, then

    b. Return FulfillPromise(promise, resolution).

12. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob, «‍promise, resolution, thenAction»)

If I read the spec correctly, it means every async function will return a native promise. But in the async body, every promise compatible object can be awaited or returned.

Thanks @aluanhaddad for patient explanation! Learn a lot from your comment.

I am pretty sure I learned it from someone else, perhaps a TypeScript team member, but I forget. Regardless, you are most welcome 😉

If I read the spec correctly, it means every async function will return a native promise. But in the async body, every promise compatible object can be awaited or returned.

I believe you are reading that correctly. It does seem to suggest that it is indeed only top level async bodies which are restricted to returning instances of the global Promise type.

The behavior of TypeScript's implementations matches your reading.
With "target": es2015"
_TypeScript_

import Bluebird from 'bluebird';

function getValue() { // The return type is inferred as Bluebird<number>.
  return Bluebird.resolve(4);
}
async function getValue() { // The return type is inferred as Promise<number>.
  return await Bluebird.resolve(4);
}
async function getValue1() { // The return type is also inferred as Promise<number>.
  return Bluebird.resolve(4);
}

Interestingly with "target": "es5" and "lib": "[ "es2015" ]" we are allowed to mix and match with async functions returning the result type of propagating their awaitable but only when explicitly specified.
_TypeScript_

import Bluebird from 'bluebird';

// The return type annotation changes the emit. This doesn't happen anywhere else in TypeScript.
// If we do not have a `Promise` declaration in scope, like if we set `"lib": [ "es5" ]`, 
// we will get a compiler error unless we specify the return type explicitly.
async function getValue(): Bluebird<number> { // The return type is declared as Bluebird<number>.
  return await Bluebird.resolve(4);
}

_ES5.1_

import Bluebird from 'bluebird';

function getValue() {
  // And it indeed returns a Bluebird!
  return __awaiter(this, void 0, Bluebird, function () {
      return __generator(this, function (_a) {
          switch (_a.label) {
              case 0: return [4 /*yield*/, Bluebird.resolve(4)];
              case 1: return [2 /*return*/, _a.sent()];
          }
      });
  });
}

EDIT:
This interaction between the ambient global declaration and the explicit return type annotation of the async function is confusing and can be abused in weird ways.

I think I ran into this too - or possibly a related issue - because I wanted to use mocha-typescript and to have my test suite import my app code, while having my test project be its own typescript project.

This prompted me to add declaration: true to my src project's tsconfig.json so my test project can consume/reference it.

And my main application's entry point exports an async function as its default export like this:

import * as Promise from 'bluebird';
/* ... all imports and stuff... */
import * as feathers from 'feathers';

const feathersApp = feathers();

const configApp = async (app: feathers.Application) => {
  /*... await some stuff.... await more stuff... */
  return app;
}

export default configApp(feathersApp); /* ERR with bluebird as Promise (w/ declarations) */

Getting error - which I suppose I could sidestep with the aforementioned wrapper? Or possible a declaration...?

declare var Promise: bluebird<any>;

with import * as bluebird from 'bluebird' instead of importing bluebird as promise?

Version deets

node.js: 8.0.0
npm: 5.0.2
tsc: 2.3.4
VSCode: 1.13.0

Both tsconfigs targets es2017.

Any advice? Thanks for reading 😸

Seems like most people got this sorted out and native Promise implementations are everywhere now, so I don't think there's any further action warranted from our side. Thanks everyone for being a helping community!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fwanicka picture fwanicka  ·  3Comments

kyasbal-1994 picture kyasbal-1994  ·  3Comments

weswigham picture weswigham  ·  3Comments

siddjain picture siddjain  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments