Definitelytyped: bluebird 3.0: how to use it as overload for global Promise?

Created on 24 Aug 2016  ·  44Comments  ·  Source: DefinitelyTyped/DefinitelyTyped

Hey everyone!

I am using bluebird as a replacement for global Promise object. I'be tried to update typedefs to latest version, published by @lhecker, and ran into problem: global Promsie is not overloaded now by default.
How cam I achieve previous behaviour? Maybe we could have bluebird-global.d.ts, for example?

Most helpful comment

Hi guys,

@types/bluebird-global is now available. These typings use @types/bluebird@^3.0 under the bonnet and allow you to use bluebird's methods on the global Promise (i.e. ts compilation doesn't fail).

Please read this to see how to use it.

All 44 comments

Well IMHO the previous Bluebird definitions weren't a good solution either, because they heavily leaked into the global namespace and I think it was a good idea to reduce redundant efforts.

The way the previous definitions worked though is by defining declare var Promise: PromiseConstructor; while PromiseConstructor was the previous (globally defined) Bluebird interface.

I.e. if you create a local *.d.ts file and add something like this it might work maybe?

import Bluebird = require("bluebird");
declare var Promise: Bluebird<any>;

it might work maybe?

unfortunately, no. Because I have a lot of code, which written like this:

declare function doLoadData(): Promise<Data>

as you can see, function returns Promise<T>, which is standard Promsie, not bluebird one. declaring var Promise: Bluebird<any> I will overload standard Promise constructor, not interface.

I switched back to the 2.0 typings for this reason.

@Strate Ah damn I wrote a lengthy comment about what I think about this and what we should try to do. But it seems I forgot to submit it and it was finally lost...

A lot of people seem to have this problem.

I created a repo that shows the issue: https://github.com/d-ph/typescript-bluebird-as-global-promise

git clone && npm install && npm run tsc

The problem:
3rd party d.ts files are typed against Promise. This Promise is either defined by typescript's lib.es6.d.ts (when "target": "es6") or by other libs, e.g. core-js (very popular, when compiling to es5 with typescript). Latest bluebird.d.ts isn't declared as global Promise, even though bluebird.js exposes itself as the global Promise.

Result:
Devs can't use bluebird's functionality on the Promises returned from 3rd party code (compilation fails).

Expected result:
Devs can use bluebird's functionality on the Promises returned from 3rd party code (compilation succeeds).

I'm not sure who's the maintainer of bluebird typings. @lhecker , you're the unlucky person to be returned by git blame. Could you tell us, what's the preferred why of using bluebird typings in such a way, that the github project, I linked above, compiles?

Current walkarounds:

  1. The dirtiest. Never import bluebird and never use bluebird typings. For bluebird Promise functions use this: Promise["config"]({}); Promise.resolve("foo")["finally"](() => { console.log("lol"); }) to silent the compiler. I.e. use the array access operator: [""]
  2. Dirty and annoying. In every entry ts file in your application add these two lines:
import * as Bluebird from 'bluebird';
declare global { export interface Promise<T> extends Bluebird<T> {} }

For Bluebird's static functions, use Bluebird.config({}) instead of Promise.config({}).

The downside is, that IDEs struggle to interpret this hack correctly.

Hmm... It was kinda unforeseen for me that you guys will have issues with this, since you're all using Bluebird in a way I and many others don't. In fact I didn't even write the typings myself! I simply copied the only existing ones for 3.0 from here, because having any typings for 3.0 is better than having none, right?

The thing is that the current direction TypeScript is headed is clearly modules > globals which is something I really approve of. But this also means that modules shouldn't ever modify global objects, especially if you consider that Bluebird does not replace the global Promise in every case! Or to put it that way:

What happens to your "type safety" if you call Promise.noConflict() literally anywhere in your code? It will revert the global Promise type back to the original one and make your code crash, even though tsc told your everything is fine.

So yeah... @d-ph. Your second walkaround is what you should have considered to be doing all along, since it's in the spirit of module systems. But I know that this is only the ideal solution for libraries, while it _can_ be really annoying for applications. I agree that application systems should definitely at least be able to replace the global Promise object and then also have the matching typings for that use case as it was available in 2.0.

In the end I think that in the light of TypeScript's ideology extending the global Promise type should be done _very_ carefully (remember the noConflict() problem etc.) and if so only as opt-in.

IMO the way forward is to write an bluebird-global.d.ts (or similar) file of some sort which extends the global Promise object with the same interface declarations found in the bluebird.d.ts file. And if you need to use those you should have to explicitly import them instead of having them always included. That way you can have safe _and_ correct typings for most use cases and especially when writing libraries, while having access to the added benefits of overwriting the global Promise in applications.

If you think that this idea is a good one and if you have some spare time left, it'd be cool if you could create a PR. I'm sure many would be really happy about such a contribution. 🙂

I'm phrasing it this way because I'm currently not in the position to write those typings, due to lack of time and not needing such typings at the moment. I hope you can understand that.

@lhecker I think that I could agree with you. Because if we have global override of Promise to bluebird's one, we will only hack typescript compiler, but not the real world. For example, with overriden Promise typescript will think that fetch returns bluebird's one:

import `whatwg-fetch`;
let result = fetch("anyurl"); // this is NOT bluebird promise, but typescript think that it is.

Without wrapping fetch to bluebird's Promise.resolve you will not get, for example, .finally method on result:

import `whatwg-fetch`;
fetch("anyurl").then().finally() // goes to runtime error with overriden global promise, but should be compile error.

So, I think explicitly importing bluebird on each usage is better solution:

import Promise from "bluebird";
import `whatwg-fetch`;
Promise.resolve(fetch("anyurl")).then().catch() // no compile error, no runtime error

I'm gonna to refactor my code.

Thanks for your answer, @lhecker .

I agree with everything you said. And I like @Strate 's solution of wrapping 3rd party's code with the Promise.resolve() method, to transform es6 promise to Bluebird's (or Bluebird's to Bluebird's, because I want to keep Bluebird's promise global in the runtime, so I don't need to rely on 3rd party code to handle their errors correctly, but that's beside the point).

It looks like I didn't know how to do this correctly. What I think others would benefit from is more documentation on how to deal with this problem, because everyone coming from browser programming world (as opposed to: from nodejs/typescript) hits it after they:

  1. npm install <absolutely everything that uses es6 promise>
  2. npm install bluebird @types/bluebird
  3. use the 3rd party code with typescript

I'd also benefit from having this "How to use in case 3rd party code is typed against es6 Promise" section documented somewhere for future reference. I.e. having the

import * as Bluebird from 'bluebird';
declare global { export interface Promise<T> extends Bluebird<T> {} }

and

import * as Promise from 'bluebird';
import { Observable } from "rxjs";

let observable = Promise.resolve(new Observable<number>().toPromise());

in a readme or in the doc block at the top of bluebird.d.ts file.

What do you think?

I've completed moving from global overriding of Promise to bluebird ones, and I've found some issues, where 3rd party libraries returns ES6 Promise, which was tredated as bluebird's one. So, making that move cleaned up my codebase as well. I would recommend to everyone move from global overloading of Promise. Cheers :)

I understand modules > globals but let's say for the sake of argument (and/or reality) that I'm working on a large-ish browser SPA and I've been tasked with using Bluebird as our Promise polyfill.

I'm trying the bluebird-global.d.ts fix suggested by @lhecker with the content from @d-ph:

import * as Bluebird from 'bluebird';
declare global { export interface Promise<T> extends Bluebird<T> {} }

I've installed it via typings which generated my typings/modules/bluebird-global/index.d.ts:

// Generated by typings
// Source: src/bluebird-global.d.ts
declare module 'bluebird-global' {
// via https://github.com/DefinitelyTyped/DefinitelyTyped/issues/10801
import * as Bluebird from 'bluebird';
global { export interface Promise<T> extends Bluebird<T> {} }
}

However, when I try to build everything, TypeScript (v1.8.2) complains:

ERROR in /path/to/typings/modules/bluebird-global/index.d.ts
(6,27): error TS2665: Module augmentation cannot introduce new names in the top level scope.

ERROR in /path/to/src/bluebird-global.d.ts
(2,35): error TS2665: Module augmentation cannot introduce new names in the top level scope.

I took a look at the MS example for global-modifying-module.ts
https://www.typescriptlang.org/docs/handbook/declaration-files/templates/global-modifying-module-d-ts.html

And a TS issue related to this error message
https://github.com/Microsoft/TypeScript/issues/6722

But I'm at a loss for what I should be doing. Can anyone help?

Hi.

Could you check my repo, that shows the problem and the solution? link. I was quite happy with it, until I decided to wrap all promises from 3rd party into bluebird's Promise constructor, which is what I do now. Could you confirm, that after you follow the steps in the readme, you can't compile, but after you uncomment the

// declare global {
//     export interface Promise<T> extends Bluebird<T> {}
// }

it compiles?

Keep in mind, that my repo uses TS 2 (which is now stable) and you said you're using 1.8.2. Just check what happens, when you upgrade TS to 2.

FInally, I had some problems with putting my solution in my global d.ts file. I ended up adding it to every entry point of my webpack compilation, which solved the problem (which makes sense to me now). I don't know your js setup, but could you try to put my fix in every file, that fails during the compilation (or at least one of them) and check whether it helps?

I also want to be able to "register" Bluebird's Promise implementation as the global Promise. I read this entire thread but I don't follow one part. The part suggesting that 3rd party libraries will still return the native (e.g. non-Bluebird) Promise implementation. How could this be, when that 3rd party code is at some point invoking the Promise constructor (new Promise(...)) which has been replaced with the Bluebird implementation at the global level?

<script src="//.../bluebird.min.js"></script>
<script>
    var promise = fetch("some url");

   promise.finally(...); 
</script>

Shouldn't this work fine since I've included Bluebird which has replaced the native Promise implementation?

Finally, if I am doing this full global replacement at runtime, I should be able to inform TypeScript about this so that at compile time, all Promises are replaced with Bluebird as well.

What am I missing?

If you use the dist version of bluebird (which you do), then 3rd party libraries will use Bluebird, since the global Promise is now Bluebird. You get this part right. People mention otherwise, because they talk about the node.js usage of Bluebird.

This whole thread is about the not-so-obvious way of making ts compile with that assumption (that global Promise is Bluebird). If you managed to do it (e.g. via the declare global {} thing), then you're done.

@d-ph But my takeaway is that I have to do that in every *.ts file rather than just once -- is that correct? Maybe a summary of the final "solution" to this problem would be good. :)

Yeah, you see, there is no simple just copy&paste this line to your X file and everyone and their dog are happy now kind of solution here. Or at least I'm not aware of it.

The last time I checked you either:

  1. copy&paste the import * as Bluebird from 'bluebird'; declare global { export interface Promise<T> extends Bluebird<T> {} } line to every entry point *.ts file, or
  2. wrap every promise returned from 3rd party code in Bluebird's constructor function. In the runtime it will do nothing (except for unnecessary runtime overhead) and in the compile-time it will make TS happy.

Again, solution 1. requires putting that code only in entry point files, not every file. At least, this is what worked for me (webpack + awesome-typescript-loader).

If you find another solution, that literally requires devs to put only 1 line in 1 file, then please do share with the community ;p

Thanks @d-ph -- can you confirm what you mean by "every entry point *.ts file"?

Yeah. "Entry point" .ts file is the .ts file, that you load in your html via <script> tag. In other words, this is the file from which the compilation starts.

After a quick googling just now, I found this (don't bother reading it). The bottom line is, that if you manually add global { export interface Promise<T> extends Bluebird<T> {} } in the bluebird.d.ts, then you don't need to mention this again anywhere else. I don't have time to test it right now, but I did test it with the test github repo I created, and it seems to work.

In other words, download bluebird.d.ts and change this:

// Generated by typings
// Source: bluebird.d.ts
declare module 'bluebird' {
// Type definitions for Bluebird v3.x.x
// Project: http://bluebirdjs.com

class Bluebird<R> implements Bluebird.Thenable<R>, Bluebird.Inspection<R> {

to this:

// Generated by typings
// Source: bluebird.d.ts
declare module 'bluebird' {
// Type definitions for Bluebird v3.x.x
// Project: http://bluebirdjs.com

global { export interface Promise<T> extends Bluebird<T> {} }

class Bluebird<R> implements Bluebird.Thenable<R>, Bluebird.Inspection<R> {

Obviously this is not ideal, especially if you use @types/bluebird (which you'd need to remove now, because you'd use your custom hacked bluebird.d.ts), but well...

So I already have a _stubs.d.ts file to which I added the following, and I am a lot closer. No more complaints about finally not existing on Promise, but for some reason, I still get errors about delay not existing on Promise. I didn't have to edit bluebird.d.ts. Will investigate but this may be a great solution!

declare module "bluebird-global" {
    import * as Bluebird from "bluebird";

    global { export interface Promise<T> extends Bluebird<T> { } }
}

Edit: My issue with delay was because I was calling it staticly, e.g. Promise.delay(2000).

With the solution I posted above, I am getting this error when my function returns a Promise<T>:

error TS2322: Type 'Bluebird' is not assignable to type 'Promise'.

I guess this is because now that I've replaced Promise with Bluebird, whenever I use then, etc., the return value is a Bluebird<T> instead of Promise<T>.

Here's my final version of this hack. I don't like doing this but I like it better than the other options. Basically, I have to re-iterate things I'm using on the interface, changing the return type to Promise instead of Bluebird. This is a straight copy and paste from the Bluebird definition file other than that.

_stubs.d.ts

declare module "bluebird-global" {
    import * as Bluebird from "bluebird";

    global {
        export interface Promise<T> extends Bluebird<T> {
            then<U1, U2>(onFulfill: (value: T) => U1 | Bluebird.Thenable<U1>, onReject: (error: any) => U2 | Bluebird.Thenable<U2>): Promise<U1 | U2>;
            then<U>(onFulfill: (value: T) => U | Bluebird.Thenable<U>, onReject: (error: any) => U | Bluebird.Thenable<U>): Promise<U>;
            then<U>(onFulfill: (value: T) => U | Bluebird.Thenable<U>): Promise<U>;
            then(): Promise<T>;

            finally<U>(handler: () => U | Bluebird.Thenable<U>): Promise<T>;
        }
    }
}

It would be great to have an official bluebird-global or bluebird-override definition which looks much like the existing definition but uses Promise everywhere instead of Bluebird.

Glad you were able to find a solution.

Just for completeness: as @ProTip said, using bluebird-2.0.d.ts JustWorksTM. Just install it with npm install @types/[email protected] and add it to tsconfig.json's compilerOptions.types:

{
    "compilerOptions": {
//     (...)
        "types": [
          "bluebird"
        ]
    },
    "include": [
        "src/**/*.ts"
    ]
}

Any differences between that .d.ts and current Bluebird version (i.e. 3.x) I recommend to hack manually.

By JustWorksTM I mean: it works for:

a) es5 target
b) es6 target
c) es5 target with core-js lib

regardless of whether someone uses a build setup (webpack + awesome-typescript-loader) or not. Additionally, PhpStorm IDE is not confused at all.

I spent some time today looking at this issue and in effect created/updated those two ticket in Microsoft/TypeScript: https://github.com/Microsoft/TypeScript/issues/10178 and https://github.com/Microsoft/TypeScript/issues/12382 . My idea is, as you said (and some before you), that we need a bluebird-global.d.ts file. To avoid duplicated code, I found, that this would work:

// bluebird-global.d.ts

import * as Bluebird from "bluebird";

export as namespace Promise;
export = Bluebird;

provided that the two aforementioned tickets are solved or workarounds are found. In the meantime I recommend using the bluebird-2.0.d.ts, when coding for the browser.

@JoshMcCullough

How could this be, when that 3rd party code is at some point invoking the Promise constructor (new Promise(...)) which has been replaced with the Bliebird implementation at the global level?

Pretty easy. Try in your browser's console (Chrome preferred):

var NativePromise = Promise;
window.Promise = function() {}; // try to overload NativePromise
NativePromise === Promise; // false. Seems it is overloaded!
// And now, let check with some native code, which return Promise, for example fetch
Object.getPrototypeOf(fetch("")) === Promise.prototype; // false, Whoops!
Object.getPrototypeOf(fetch("")) === NativePromise.prototype; // true! Double whoops!

Hi. I have been doing this and it's pretty smooth. First of all, do not depend on bluebird for library projects. For the application project, import bluebird but not the typings. At the entry point of your application, do this:

global['Promise'] = require('bluebird')

This will replace the global promise object for the application and all included libraries.

Hi guys,

@types/bluebird-global is now available. These typings use @types/bluebird@^3.0 under the bonnet and allow you to use bluebird's methods on the global Promise (i.e. ts compilation doesn't fail).

Please read this to see how to use it.

Awesome, thanks @d-ph!

@d-ph Thanks for the @types/bluebird-global. Do I need to do any sort of import and reassignment in my project to use bluebird as a replacement for the global Promise?

npm install --save-dev @types/bluebird-global and then follow the instructions I included in the typings: link (link updated 2017-04-02). This alone should do the trick (i.e. no manual imports/reassignment should be needed).

As a side note: you don't need to mention @types/bluebird in your package.json::devDependencies anymore, because this is implied automatically.

Update to link in the previous comment: link

@MichaelTontchev @d-ph any chance we could get the Promise.Inspection interface added to bluebird-global?

Hi @ksnyde.

Please talk with me. I'm the maintainer.

Could you confirm, that you're referring to this Promise.Inspection, please?

All methods from that interface are exposed via bluebird-global. I.e. the following will compile:

let promiseInspectionTest = new Promise<void>((resolve) => {});
promiseInspectionTest.value();

So it seems to me, that you're requesting this to be exposed as Promise.Inspection from bluebird-global.

Could you tell me, whether this is a major setback for you to be using the following instead:

import * as Bluebird from "bluebird";

class Foo<T> implements Bluebird.Inspection<T> {

}

Since you're using bluebird-global, you are able to also import the original bluebird typings like that without any explicit devDependencies.

I'd rather not extend the global Promise any further without solid reasons behind it, because it's a subtle art to make those typings work with the standard Promise typings from lib.d.ts. I really recommend accessing this interface from the bluebird typings directly, because one day JavaScripts gurus might add Promise.Inspection to the standard, which would break the bluebird-global typings, which in consequence would cause unnecessary problems to the end users.

Also, even if I were to add the interface, you would need to wait unspecified amount of time for it to get merged to master, because DT's maintainers are kind of busy with PRs these days.

Cheers.

I have indeed been using the approach you discussed for the time being and it is an adequate work around. Or maybe "work around" is the wrong nomenclature.

My understanding/perception was that the idea behind bluebird-global was to expose the superset of Promise functionality that bluebird provides in which case as a user I _would_ expect Bluebird.Inspection to be exposed at Promise.Inspection. However, if your intent is rather to just ensure the official API surface of Promises uses Bluebird then I guess this "work around" is actually an appropriate long term solution.

While I prefer my interpretation I'll be fine using the solution presented here if need be.

While I certainly see where you're coming from with your expectations, the main reason why I created bluebird-global was to let TypeScript know, that Promises created and returned from third party code are actually instances of Bluebird promises, to which there was no other _not annoying_ alternative, but to expose all the Bluebird's instance and static methods on the global Promise symbol. As I mentioned previously, the way it's done is not a simple class Promise<T> extends Bluebird<T> {} (although it was originally), but rather a carefully patched global Promise symbol. And as I mentioned, I'd rather avoid having to maintain anything, for which there is a known alternative, that doesn't literally make you pull your hair out your head.

Sorry for saying "No" to this one. If there are more people, requesting this particular feature, I will reconsider adding it. I don't want to sound authoritative here -- this is an open-source project and anyone could probably push this feature in. The point I'm trying to get across here is, that in my opinion the benefit of having this in does not outweigh the cost of maintaining it.

Cheers.

makes sense. thanks for the thinking behind your approach.

@d-ph does the following make any sense to you ... I'm getting an error stating that "reflect" is not a function on the code below:

image

And while in the editor's intelisense it is correctly identifying that the property p in the mapping function is a Bluebird promise and has the extended API surface only found on Bluebird (versus Promise).

image

I just can't make heads or tails out of it. I did notice that when I inspect the _type_ of the map's iterator variable it does show up as:

image

I supposed this is ultimately why I'm getting the limited API surface defined bybluebird-global but I don't know why it isn't resolving correctly.

Hi.

I'm not able to reproduce :/ Those code snippets work in my setup.

First of all. you're not using bluebird-global in your allSettled() function. You type directly using Bluebird. This is not a problem, but maybe you were not aware of it. The following snippet uses bluebird-global:

function allSettled<T>(promises: Array<Promise<T>>) {
    const reflections = Promise.all<T>(promises.map((promise => {
        return promise.reflect();
    })));

    return reflections;
}

I.e. that snippet types to the global Promise (which is patched with Bluebird's methods in bluebird-global.d.ts). As I said: this one is for your information, in case you weren't aware of it, because both your and mine snippets work the same.

Back to the problem of missing Bluebird's methods. My guess is: you don't replace global Promise with Bluebird in runtime and then you run allSettled() with Promises constructed using the global Promise instead of Bluebird.

Let me show you my code and some screenshots.

function allSettled<T>(promises: Array<Promise<T>>) {
    const reflections = Promise.all<T>(promises.map((promise => {
        return promise.reflect();
    })));

    return reflections;
}

let promises = [
    Promise.resolve(),
    Promise.reject(new Error("rejected test")),
    new Promise<void>(() => {}),
];

let reflections = allSettled(promises);

console.log(reflections);
// this is part of my entry point

/*
 * Promise
 */
import * as Promise from 'bluebird';
import 'expose-loader?Promise!bluebird';

image
_Pic 1: The promise in the Array.map() is Bluebird (you can tell by the presence of the underscore properties like: _bitField)_

image
_Pic 2: Promise.reflect is indeed defined in the loop_

Could you set a js breakpoint like me in Chrome Dev Tools, and see what is the promise inside the .map? Better yet: could you just type Promise in the console and see whether you get the following output:
image

If you get the following output, then you did NOT replace global Promise with Bluebird in runtime:
image

In which case you need to do it. E.g. like me in your entry file.

Finally, just a bit of a dev feedback: I'd personally use Promise<T>[] instead of Array<Promise<T>>. But this is down to devs' preference of course. I just come from C background, where there is no templating and where devs use the array-access operator to define types.

Cheers.

Thanks so much for such a thorough explanation. One thing I wasn't sure about though was what does the following point to:

import 'expose-loader?Promise!bluebird';

@d-ph ahhh, the one liner above was what I was missing. Would never have gotten there without your help! It may just be me but I think it would be helpful if the README text had a reference to the use of Expose Loader.

Although to be fair, i'm still not 100% if this then requires the use of webpack? My target is not the browser, just a node function.

In fact, it does look like it's not fully working as I get the following error when I try to execute the file (no errors in the editor before run time):

Error: Cannot find module 'expose-loader?Promise!bluebird'

You're right, expose-loader is a webpack thing (a webpack loader). So if you don't let webpack process that import statement, it won't run (i.e. you'll get the "Cannot find module" error).

If you can't/don't use webpack, you'll need to find another way of making the global Promise be Bluebird in runtime. I don't have much experience with node, but I found this just now: https://github.com/petkaantonov/bluebird/issues/1026 (overriding Promise in node).

I recommend finding out how to use async/await in latest node 7. You're lucky enough to live in times, when this is available for devs.

As to mentioning using expose-loader in the README: I already state at the top of the d.ts file, that this is devs' job to replace Promise with Bluebird in runtime: link. Since there are so many ways of doing it, I didn't actually mention any of them. Keep in mind, that expose-loader is just an equivalent of running window.Promise = Bluebird. Well, hopefully google will index my reply, so people won't be looking for possible options for too long anymore ;p

@d-ph looking forward to native async-await but all these functions are on AWS Lambda so I'm locked to node 6.10.x for now. In a future pass of these functions I'll probably switch to async-await anyway and have Typescript transpile it to ES2015 but don't want to introduce this just yet.

Anyway, thanks for the link @d-ph, I'll give that approach a try. Oh and in case anyone's interested the final version of these functions (which are quite handy in promise-land):

export function allSettled<T>(promises: Array<Promise<T>>) {
  const reflections = Promise.all<Promise.Inspection<T>>( promises.map((p => p.reflect())) );
  return reflections as Promise<Array<Promise.Inspection<T>>>;
}

export function settleProps<T>(promiseHash: IDictionary<Promise<T>>) {

  const reflections: IDictionary<Promise<Promise.Inspection<T>>> = Object.keys(promiseHash)
    .reduce((newObject: IDictionary<any>, key: string) => {
      newObject[key] = promiseHash[key].reflect();
      return newObject;
    }, {} as IDictionary<Promise<Promise.Inspection<T>>>);

  return Promise.props(reflections) as Promise<IDictionary<Promise.Inspection<T>>>;
}

Full intelisync/type information is available which is super nice.

@d-ph hopefully it's ok if I revive this with a relevant question...

When I attempt to use bluebird-global, I appear to be getting bit by definition differences in both then and catch. I see that those are handled specially in bluebird-global, but it seems to limit me to the standard Promise api instead of getting bluebird. For example:

Promise.resolve(true).catch(Error, () => false)

fails as catch only expects 1 argument.

And another issue:

Promise.resolve([3]).map((n: number) => true)

Fails with:

│TS2345: Argument of type '(n: number) => boolean' is not assignable to parameter of type 'IterateFunction<{}, boolean>'.           │
│  Types of parameters 'n' and 'item' are incompatible.                                                                             │
│    Type '{}' is not assignable to type 'number'.                                                                                  │

Should those work or am I doing something wrong? They work at runtime, they just don't typecheck.

Thanks!

Hi,

About the .catch(Error, function), you are correct by saying that .then, .catch (and more) are handled differently than the rest of the Bluebird functions. However, the specific .catch(Error, function) override is included in bluebird-global. I double-checked and I am able to compile:

Promise.resolve(true).catch(Error, () => false)

I checked with TS 3.0.1 and 2.9.2. I don't know why you might be running into a problem here. Maybe there's something specific in your TS project that overrides the global Promise after bluebird-global. I don't know. Maybe try to narrow down what's causing the problem by starting from a very basic TS project and then adding more dependencies from your current project, and see at which point it breaks.

About the other problem: I don't know why it doesn't work. And yes, it should work. Please create a github issue for it and we'll go from there. Here are some facts about the problem:

  1. All bluebird-global does with .map() is just reusing bluebird.d.ts's .map() type definition. In other words, the defect shouldn't be coming from the bluebird-global typings.
  2. The line you mentioned fails on Promise.map(), but it works on Bluebird.map():
import Bluebird = require('bluebird');

Bluebird.resolve([3]).map((n: number) => true); // works

Promise.resolve([3]).map((n: number) => true); // fails
  1. After spending some time to decrypt the typescript's problem (essentially: why TS concludes that the parameter of n should be {}), I concluded that bluebird.d.ts also shouldn't work, but it works for unknown to me reason. Long story short, the following is what the .map() is typed to after removing all the layers of abstractions:
map<U>(
    mapper: (
        item: U,
        index: number,
        arrayLength: number
    ) => U | PromiseLike<U>,
    options?: Bluebird.ConcurrencyOption
): Bluebird<T extends Iterable<any> ? U[] : never>;

It says that the return type of the mapper function should be the same (or a Promise of the same) as the type of the item. In your example, the type of item is number and the return type is boolean. I am unable to comprehend why this compiles when using Bluebird directly, but it doesn't when using the global Promise. By the way, it still doesn't work, when I change the return type in your example to be any number. It does, however, work, when I say that the item can be of type any. There's something wrong with bluebird.d.ts's type IterableItem<R> and its usage in this context.

@d-ph


Edit:

I checked the map()'s "unlayered" form again, and the mapper function isn't typed to have the same return type as the type of item (I thought it was). My bad.

All bluebird-global does with .map() is just reusing bluebird.d.ts's .map() type definition.

Is the problem that when you copy the type out of the generic class Bluebird<R> it defaults to {} for R since it can't infer it from the parent?

I wonder if map: typeof Bluebird<T>.prototype.map would work? (I haven't tried this yet)

IMPORTANT:
If using @types/bluebird-global, delete form your dependencies @types/bluebird as said by @d-ph

npm install --save-dev @types/bluebird-global and then follow the instructions I included in the typings: link (link updated 2017-04-02). This alone should do the trick (i.e. no manual imports/reassignment should be needed).

As a side note: you don't need to mention @types/bluebird in your package.json::devDependencies anymore, because this is implied automatically.

Having both was causing a miss match between the types return by @types/bluebird and my global Promise (@types/bluebird-global)

Was this page helpful?
0 / 5 - 0 ratings