-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Symbol.toStringTag support to Promise #1421
Added Symbol.toStringTag support to Promise #1421
Conversation
Any chance of getting this merged? This is a real pain when using bluebird in Typescript. |
@petkaantonov? I'm +0 on this since it's a used and known trick for detecting non-native promises vs. native ones. Implementation looks correct. |
This implementation returns |
Simply "Bluebird" might make more sense, since this is the bluebird constructor name. Is there any way I can help with this? (Also keen to fix typescript issues). |
I suspect that's not a good idea. Promises return const Bluebird = require("bluebird");
const bluebird = new Bluebird(() => {});
const native = new Promise(() => {});
console.log("bluebirdtoString", bluebird.toString());
console.log("native toString", native.toString());
console.log("bluebird O.call", Object.prototype.toString.call(bluebird));
console.log("native O.call", Object.prototype.toString.call(native)); Two other examples of why giving something different might be funky:
Still, I haven't researched this much beyond those suspicions. It'd be great if someone proved me wrong - I'm up for updating this PR if so. |
You're right about TypeScript - the type is specified as a literal, and your reasoning makes sense. |
If the only objection to this PR is that is makes it harder for users to detect if the promise library is Bluebird or Native, then why don't we just include a property on the Bluebird object called |
Someone might be checking for native promises by checking the object toString name, with this they'd have to always consider bluebird.
For example, if it is not a native promise they might need to hook it differently for instrumentation, coerce it or behave differently (for example if they're adding properties on it which might collide). |
Ping @petkaantonov, could you please merge this in? |
+1 to merge request! |
++ |
@benjamingr would you mind looking at this? I think it is fit to be merged. |
Would really appreciate this getting merged in some time. I feel like bluebird is a real pain to use in typescript because of this compatibility issue. |
wait, what? |
I use bluebird with TS all the time and I haven't noticed |
Here's a minimal example: const x = new Bluebird(() => { return; });
function takePromise<T>(a: Promise<T>) {
return a;
}
takePromise(x); This fails to compile with the error:
Bluebird promises are not currently compatible with the Promise interface, so any third party library that returns a non-bluebird promise will have to be wrapped in a bluebird promise, and bluebird promise types have to be used everywhere. How is it a subjective change to expect a promise library to be compatible with the interface of a promise? |
Casting an external promise type to bluebird would still be necessary to use any bluebird specific methods. For taking promise arguments you can use A second option is to use a definition file that simply claims that This might be an acceptable breaking change, but might also necessitate a major version bump. Its difficult to say if the compatibility breakage is too bad. We might be able to get away with redefining |
That makes sense to me. Re: PromiseLIke. I have had issues with using it in place of Promise with Bluebird: function returnPromiseLike(): PromiseLike<string> {
return new Promise<string>((resolve) => resolve('foo'));
}
function returnPromiseLikeBluebird(): PromiseLike<string> {
return returnPromiseLike()
.then((foo: string) => {
return new Bluebird<string>((resolve) => resolve('bar'));
});
}
I don't know enough about the types to debug this. My life using Bluebird + Typescript has been constant juggling of types between Bluebird, Promise and PromiseLike. 😞 |
I got this problem when I've tried to use interface ESPromise<T> extends Promise<T> {}
declare module "sequelize" {
interface Promise<R> extends ESPromise<R> {
[Symbol.toStringTag]: "Promise";
}
} @peterjwest I hope this can help you |
@peterjwest that actually seems to be a bug with the TypeScript compiler. Try copying the exact same interface as import * as Bluebird from 'bluebird'
interface PromiseLikeFixed<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => PromiseLike<TResult1> | TResult1) | undefined | null,
onrejected?: ((reason: any) => PromiseLike<TResult2> | TResult2) | undefined | null):
PromiseLike<TResult1 | TResult2>;
}
function returnPromiseLike(): PromiseLikeFixed<string> {
return new Promise<string>((resolve) => resolve('foo'));
}
function returnBB() {
return Bluebird.resolve('s')
}
function returnPromiseLikeBluebird(): PromiseLikeFixed<string> {
return returnPromiseLike()
.then((_foo: string) => returnBB())
} Seems to have already been reported here: microsoft/TypeScript#17862 |
Awesome, thanks for the info! |
Is it planed to merge that PR? I would like to see that! The suggested solution, to use |
I'll throw in my solution for the Typescript issue.
I haven't had any adverse effects doing this, and everything, including async/await (even combined with try/catch/finally) seems to work just fine now. Sorry, just couldn't wait forever for this pull request to be completed. I got work to do. |
@JoshuaKGoldberg if you are still interested in this, I have two questions
|
Still interested!
I don't think we can change the behavior of that. Some sample outputs in Node: > Object.prototype.toString.call({ toString: () => "hi" })
'[object Object]'
> Object.prototype.toString.call(undefined)
'[object Undefined]'
> Object.prototype.toString.call("")
'[object String]'
> Object.prototype.toString.call(new (class Promise { }))
'[object Object]'
> Object.prototype.toString.call(new Promise(() => { }))
'[object Promise]' Edit: Oh, I see what you're saying. Yes, this gives it the same class MyPromise { }
Object.defineProperty(MyPromise.prototype, Symbol.toStringTag, { get: () => "Promise" });
(new MyPromise()).toString()
// '[object Promise]'
I haven't seen such a thing. Perhaps someone else on this thread has? |
I have. Which is why I don't understand why this is a bluebird issue rather than something bluebird.d.ts can fix. |
@benjamingr Its because For the one you've seen, do they test for existance of the toStringTag as a property directly or are they comparing it to a value? If they're comparing it to a value, we can simply return a different value - one that would not change the current result of Object.prototype.toString.call(bluebirdPromise) - i.e. we can make it The main concern are libraries and most of those are open source. I don't think end-user apps would be a huge issue. We always have the option to bump to bluebird 4.0 |
src/promise.js
Outdated
if (typeof Symbol !== "undefined" && Symbol.toStringTag) { | ||
es5.defineProperty(Promise.prototype, Symbol.toStringTag, { | ||
get: function () { | ||
return "Promise"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the value returned is "Object"
instead, we would get the exact same external toString.call
behavior as we do now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, libraries that use x[Symbol.toStringTag] === 'Promise'
to differentiate native promises from Bluebird will continue to work normally. Only libraries that check for the mere presence of x[Symbol.toStringTag]
will have problems.
Hmm. I see we'd still have an incompatible type though. Not sure what to do about this.
It looks like TS may have made an error here and effectively introduced a silly nominal type system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrote about it here: microsoft/TypeScript#19006 (comment)
Fixes #1277.
Doesn't come with tests (yet?) - would you like me to add them, and if so where?