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
how to configure to use the standard ES6 Promise? #1588
Comments
Curious: you want to replace a faster library with a slower built-in? On the client it wouldn't matter but in node speed is a factor. What's your reasoning for this? |
@johanneslumpe It is factor in some applications, with After that it should be easy to override, which Promise library is used. |
I agree it feels pointless in a library such as knex. Given that knex's codebase already imports its own promise.js file internally, it would technically still be very easy to implement, provided the API is the same (which I'm not sure it is?). In this file one could default to global promises, else require configured library, or something like that. |
it's all about giving users a choice; for Node V6+ users, give a choice of managing 3rd party dependencies as minimum as possible.
what you're saying might be true in the past, but how about now or 6 months later? (I did some search and can only find out some 2015 benchmarks, if you have more recent comparison, please post some links) |
see Amazon's AWS-SDK: it also default to use whatever Promise is globally available; while give user a choice to configure the favorite Promise library as well http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-making-requests.html#Support_for_Promises it's all about choices |
On second thought, it won't be as easy as changing a single file. Knex currently relies a lot on Bluebird utility functions such as |
I am interested in supporting ES6 promises. Ideally we'd require a second argument to the const Promise = require('bluebird');
const knex = Knex(myKnexConfig, Promise); Perhaps we could conditionally alias |
I think this should be pretty low on the priority list, and it'd require quite a bit of internal changes, not to mention the decrease in performance (though admittedly haven't seen benchmarks in awhile) and the fact that folks might be relying on the fact that the returned promises is a bluebird (for conditional I'd be more inclined to wait until async/await lands in a stable node to look at addressing this. |
for people want the minimum 3rd party dependencies, the native Promise can be better, like https://googlecloudplatform.github.io/google-cloud-node/#/docs/google-cloud/ var gcloud = require('google-cloud')({
promise: require('bluebird')
}); |
@tgriesser I know this is an old issue, but we've now got Given that it is preferred for async/await to work with native promises (per the spec, If native promise support is something that Knex would like to have happen, but it's not currently on the radar, would a PR be welcome? Thanks for your time. |
@malexdev That being said I'm not against of dropping bluebird, but it really does need quite a bit internal changes. We might need to implement some methods that are currently exposed from |
@elhigu The main benefit for me personally is that I'm using TypeScript with a TSLint rule enforcing native promises being used for Other than that in my opinion having less 3rd party dependencies is better, and as @c0b mentioned more options is never a bad thing. I realize that it would be a lot of work, which is one reason I'm more than happy to spend time on this, if it's something that Knex is interested in moving towards. |
Yeah, I landed here coming from a typescript issue - I'm using Knex as the SQL engine for my multi-storage datastore library, and while i'm ambivalent vis-a-vis native vs bluebird promises, I can't easily use typescript on knex for this reason. I treat knex thenables as following the native spec (I don't use any of the bluebird extensions), but typescript bugs me about returning a Bluebird when the method declaration is a Promise. This is kind of two levels deep here, as we're dealing with both the promise implementation and the typings for knex (which are handled by different devs), but I'm basically stuck here - I'm technically breaking the type contract by returning a Bluebird when I've declared a Promise (I guess there's stuff in the Promise api that Bluebird doesn't support?) but I really don't feel like putting a bunch of I've spent enough time digging around in knex guts for the past year and working with promises in general that I'd be willing to take something up here and work on a PR to modularize out the Promise implementation if people want - would you be open to a PR? It'd end up being something like what @elhigu mentioned - re-implementing some of the utility functions to use whatever Promise constructor was passed in at instantiation so to avoid code-rewriting needs. Not sure about performance, of course, but that's something that can be benchmarked. Having it all done up fancy with async / await would be cool too, and I'm not in a hurry to have this fixed (ultimately for my use case I just end up flagging things as |
@ericeslinger I don't see why real Promise implementation that is being used would cause problems with TypeScript, I've been mixing bluebird and native promises a year and half without any problems... I just haven't been using any typings that would introduce types for bluebird, I just tell typescript typings that they are normal Promises and it doesn't see any difference (ofc. it will complain if I try to use bluebird's special methods). Which typescript version and is there any example project to reproduce... e.g. github repo with npm start script.
Are those knex typings from npm? Are bluebird typings from npm? Which packages? I'm having hard time to understand why that would happen. If there is separate Bluebird type it should be inherited from native promise and be ok to return from APIs that tells that they will return Promise. From the description it sounds that the problem is very broken typing implementations. And that is not relevant to this issue (typescript doesn't mind about real types, so it won't know what knex returns). |
I'm getting my typings from the DefinitelyTyped repo via installing As a specific thing, I cannot do this:
using these typings. This is because knex.select().then() is typed to return a Bluebird in the DefinitelyTyped repo, and those chain together to make more Bluebirds, and saying something like Instead I can change to
or do other tricks to wrap all calls to knex inside a native Promise.resolve() call. This will cause typescript to stop complaining downstream of my library functions, while still allowing me to use knex typings inside my library functions. Prior to yesterday, I hadn't used |
@elhigu: The issue is not broken typing implementations.
This is lying to the compiler, as Knex functions do actually return When returning a At the end of the day, the reality is there are at least two users now who are saying "Using native promises is important to us, and we're willing to put in the work to make the promise functionality more generic". I realize you're just trying to help, but frankly instead of hearing "you could do it this way" I'd like to hear whether the promise changes proposed by myself / @ericeslinger / @c0b are acceptable so that I can either start on a PR or what. |
@malexdev @ericeslinger Thanks for the more info! Looks like it actually is not possible inherit your own class from @ericeslinger Anyways this is not a problem when you create import * as Bluebird from 'bluebird';
// declaring function async converts bluebird implicitly to native Promise
async function asyncReturningPromise(): Promise<string> {
const blueBirdPromise = new Bluebird<string>((resolve, reject) => {
resolve('yay asyncReturningPromise');
});
return blueBirdPromise;
}
// main func to run the code using async / await
Bluebird.resolve().then(async () => {
console.log("await function returning promise (bluebird)", await asyncReturningPromise());
const blueBird = new Bluebird((resolve, reject) => { resolve(); });
const returnedFromAsync = asyncReturningPromise();
console.log("Bluebird instanceof Promise:", blueBird instanceof Promise);
console.log("async retval instanceof Promise:", returnedFromAsync instanceof Promise);
}); output: await function returning promise (bluebird) yay asyncReturningPromise
Bluebird instanceof Promise: false
async retval instanceof Promise: true So for now when you are using knex APIs for now you actually need to tell that you are returning Bluebird unless you are using async functions / methods, which wraps bluebird automatically to native Promises.
Actually deal with typescript is that it is enough that returned object implements the interface correctly, for example this is perfectly fine: class FakePromise<T> implements Promise<T> {
[Symbol.toStringTag]: "Promise";
then<TResult1, TResult2>(onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1> | null | undefined, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2> | null | undefined): Promise<TResult1 | TResult2> {
return new Promise((resolve, reject) => { resolve('Im totally broken and fake!); });
}
catch<TResult>(onrejected?: (reason: any) => TResult | PromiseLike<TResult> | null | undefined): Promise<T | TResult> {
throw new Error("Method not implemented.");
}
}
// this works and fake promise instance has nothing to do with native promise
function returningPromiseInterface(): Promise<string> {
const fakePromise = new FakePromise<string>();
return fakePromise;
}
// compiling this fails, because looks like Bluebird actually doesn't implement Promise interface correctly
function asyncReturningPromise(): Promise<string> {
const blueBirdPromise = new Bluebird<string>((resolve, reject) => {
resolve('yay asyncReturningPromise');
});
return blueBirdPromise;
} It messes up instanceof, but typescript actually hasn't even promised that you returned native Promise instance, only interface.
I would hate to see people having to do that kind of tricks / changing JS implementations just to satisfy bad typings.
Thanks for understanding :) Changing knex to use native promises was already started and implemented to some point last year then it was changed back by @tgriesser so I would say that for now it is better not to start this change. Also I still consider these typescript problems mentioned in this thread as a problems in typings declaration (why bluebird doesn't implement Promise correctly... I need to dig in more deep on that?), than problems in knex implementation. That being said, I'm not opposed of getting rid of bluebird in some timespan just seeing two separate issues here. |
👍 for native Promises. |
Fair enough. I still stand by my opinion that less dependencies and more choice is better, but I now see what you meant about broken typings. So still 👍 for native Promises (which I'm still willing to help with), but I see now my immediate issue can be resolved by fixing Bluebird typings. Thanks for the info. |
So I've started to use TypeScript quite a bit and I both love it and now realize the issues here are a real pain point. As async/await gains more of a foothold in recent Node land the Bluebird utility fns ( With that merged, we could then upgrade the TypeScript definitions to drop the Bluebird typings (except for If anyone has bandwidth wants to tackle this, here's what I'm thinking for a plan of action:
Let me know if this makes sense and if anyone is interested in taking a shot at this. |
Would definitely be interested to help with this. Does this mean Knex would start maintaining its own TypeScript definitions? Would allow us to do some cool stuff with generics that autogenerated typings will never support. |
I started this fork as a first attempt to add support for native Promises: |
Like this comment from 2016:
Amazing how much can change in 2 years. |
@malexdev Actually typescript uses structural typing (flow uses nominal typing and would work the way you describe) so as long as your type fulfills the |
How's this progressing? I reckon a good first step would be to factor out Bluebird specific method calls within knex (i.e. don't actually remove it yet). Removing bluebird and providing an option for a custom Promise constructor would follow (and give folks using Bluebird methods an upgrade path). I don't starting work on the first step if there are no objections. Existing work appears to have died down. |
@qubyte I don't think there is an active effort to do the change, incremental changes were made here and there, but that's about it. |
Ok. In my next chunk of free time I'll make some small-as-possible changes to factor out each method. |
@Bessonov Context? How bumping min node to 10 affects this issue? Note that we dropped node 6 support already. |
I'm not familliar with knex code base, but maybe there are some features, which can help you to get bluebird away. For example, node 10 has a support for Promise.finally. But anywa, I'm happy to see the progress on this topic 👍 |
About disposer pattern - could we just add optional callback for things, which returning disposable promise?
|
Which level of promise library independence we need?
|
What is the current state of this issue. Generally knex work now with async await but typescript will report warning that we awaiting method that is not native promise. So to answer the original issue question. Current workaround is to simply await and add something like |
@maximelkin I vote for the option 1. In long term I hope every promise library would be obsolete. |
id second that, at this point we are beyond promise polyfills even for the majority of browsers |
@Bessonov currently on knex depends libraries (and maybe projects), which requires exactly bluebird we should give some fallback solution for them |
It doesn't matter if knex's users are dependent on bluebird. Knex can still use native promises and they will interoperate just fine with bluebird promises. We should absolutely not give any fallbacks. |
So this issue started with request for feature with choosing promise implementation. But I suppose that all 1.5 typescript users are happy now. |
At least earlier with knex 0.x versions has been considered as major releases with potentially breaking changes, so only updating to 0.20.x should have been considered safe upgrade (semver is really loose when version number < 1). Removing bluebird has been on the table for a long time, it is not only about this issue.
Removing bluebird has not been for no reason. You can still externally use bluebird with knex promises. One big reason to drop bluebird has been that
Agreed. I hauled through latest changelogs... Sadly it really looks like we have failed to list breaking changes between versions. We need to be more careful when writing changelogs to really point out the changes, which breaks the old APIs. For example many of the typings changes actually will break old TS code. |
There was the same issue on ioredis project redis/ioredis@da60b8b. They wanted to support native promises - and guys made a really good solution - they added an option to support any custom promise library and they use native promise by default. Why not? Setting a custom promise library is fast and doesn't require patching all application code.
Yup. But why not wrap module calls in bluebird (or any other promise library) if it was explicitly specified? That's one simple wrapper, zero overhead, and it would allow users to use whatever promise library they want. If no one needs bluebird, no one would use this options, and you can safely deprecate it in time. Also, I saw an opinion that
But IMHO there are two wrong assumptions:
I think that's not the case for really complex applications, which go beyond async-await one liners. |
Anything which uses async-await internally is going to coerce promises to native promises, so your options become either wrapping the output of each method in the custom promise or banning async-await in internal code. It's not as small an undertaking as it might seem on first inspection. |
No, that's just as simple as I already told. You make a wrapper for exported external functions and that's all. About 10 lines of code. And write all internal code in any way you want. |
@jehy : Feel free to submit a PR for those 10 lines of code if you see a straightforward way to implement them. I'll also spend some time today trying to come up w/ a work-around. |
For what it's worth, much of the API of bluebird is duplicated with the same or close API using native promises by these packages: https://github.com/sindresorhus/promise-fun |
~50 packages instead of 1? Seriously? |
Yes, though most of the time only a few are required (p-map for example). Your mileage may vary of course. It's offered only as one potential route to what you want. |
@jehy : Here is something you can try as a temporary work-around within your application code: const Bluebird = require('bluebird');
const prototypesNeedingDecoration = [
require('knex/lib/query/builder').prototype,
require('knex/lib/schema/builder').prototype,
require('knex/lib/transaction').prototype,
require('knex/lib/raw').prototype,
];
const corePromiseMethods = ["then", "catch", "finally"];
function decoratePromiseMethods(target) {
for(const m of corePromiseMethods) {
const original = target[m];
target[m] = function(...args) {
return Bluebird.resolve(original.apply(this, args))
}
}
}
function hackBluebird() {
for(const target of prototypesNeedingDecoration) {
decoratePromiseMethods(target);
}
}
hackBluebird(); This is not really an adequate solution to the overall problem. There are other temporary objects created within |
Also, disclaimer: the work-around ☝️ has had very little testing. So, you should re-run your application's tests to ensure that nothing has broken. |
Just wanted to add my 2 cents here: i really appreciate all the work put into this migration, regardless of the negative feedback. From our app's perspective, knex was the last library forcing us to require Bluebird, and conforming to full native promise support means that:
its such a huge win to continue to run towards the es standard... and I know that isnt easy for library maintainers so i wanted to shout out to you guys and thank you for taking on such a burden! for those suffering from the change: I'd love to help since we have benefitted, so please reach out if you need help debugging or migrating! |
@chaffeqa Thank you for this feedback, it means a lot! |
@jehy : Have you had a chance to try to work-around that was proposed? If so, did it resolve your immediate issues? |
Is this effort dead? I'm struggling trying to make sense of our server's errors and it's a real problem for us. |
@jpike88 Latest knex version does not use Blubird anymore, native promises are used for everything. |
when running with node v6, is there a way to configure to use the standard ES6 Promise only (get rid of bluebird dependency)?
for example in promise-queue package, it defaults to use whatever is globally available Promise,
https://www.npmjs.com/package/promise-queue
or user can explicitly configure it by
Queue.configure(require('vow').Promise);
So can this package implement a similar strategy?
The text was updated successfully, but these errors were encountered: