-
Notifications
You must be signed in to change notification settings - Fork 12
Provide promise based API for core libraries #25
Comments
Here's one place it's been discussed: nodejs/node#11 |
But that issue is closed pending future discussion. So this is future discussion 😊 |
Right, this is a better place to discuss it. I was providing the link for reference since OP said they couldn't find any previous discussion. There's a previous discussion for context. |
@Trott thanks. Last comment seems to imply it's ok to move discussion here, so will keep open. |
I think nodejs/node#11 (comment) sums up well why Promises have no place in Node core. Node and LibUV aim to resort to the lowest common denominator and provide the smallest API possible. Adding more ways of doing the same thing that can already be achieved by userland packages is against Node's philosophy. Last I checked on the Node TC meetings the status was that promise rejection handlers will be added as V8 implements them, making Promises slightly less harmful. In terms of TC members some seem to be excited for potential async/await control flow and some are vocally against Promises as a way of handling async. I feel that this topic is completely chewed out, and this issue best be closed as I suspect it might (again) spin into a very misinformed back and forth. |
I don't think it's a matter of if promises will be implemented (sorry everyone) I think it's a matter of when. Issues and PRs like this will keep popping up until promises are integrated. That's inevitable. I say keep this issue open as it does provide an outlet (and a URL) for anyone who wants to contribute to the discussion vs. seeing a hundred more issues from now until the promise integration. |
@yoshuawuyts let's imagine for a moment that Node core was initially designed with a promise based API, almost all the same arguments could be made about supporting an errback API. Let's try:
Well, I don't have to alter that one. There are some places where promises are indeed not a good abstraction, like event and stream APIs.
In my mind, this is the only substantial argument against promises in Node. From the linked blog post:
So I guess the question is, has v8 introduced this yet? If not, I'll be happy to shut up until then :) |
FWIW, async generators are in the works, which would work pretty nicely for streams. It's like async/await, but you get an await per chunk in a for/of loop. https://github.com/zenparsing/async-iteration/#readme |
Returning a promise if a callback/errback is not passed would work. Promise-based APIs shouldn't thow, they should reject instead https://www.w3.org/2001/tag/doc/promises-guide#always-return-promises - so calls without a callback/errback ideally shouldn't throw. Would this be a compatibility risk? |
Callback vs promises will have different semantics by definition, to keep compatability with error first callbacks, we could rethrow if the callback is present. function stat(filename, callback) {
let promise;
// ...
try {
// ...
} catch (error) {
if (callback) {
throw error;
}
promise.reject(error);
}
// ...
return promise;
} |
Yeah, that's what I meant. The compatibility risk is if someone already does: whatever.asyncThing(); …where the async throw is meaningful to them, but they're happy to fire & forget the rest. The change we're proposing would change the behaviour here. |
Ah.. now I'm following you. However omitting the callback from an async function is generally an error, or simply undefined behavior. Documentation does not mark for example, the callbacks in the Taking from the C++ world, if you rely on undefined behavior you're toast. That's my two cents on how this should be treated. |
Agreed. Given that, if no callback is provided, return a promise and treat any exceptional failure as rejection, otherwise go with the current behaviour. |
I like the idea of returning a promise if no callback is provided. I think that'd be pretty backwards compatible. Also, as an equivalent of event emitters throwing when no E.g: // throws at the location of the promise
new Promise(() => { throw 'a' }).then(() => console.log('foo'))
// doesn't throw
new Promise(() => { throw 'a' }).catch(() => {}) Lastly, check out my OP here for extra arguments in favor of promises in Node: nodejs/node#4596 (comment) |
One of the main objections to this has been performance. If someone wants to put together a PR and see how it performs compared to the current API, and quantify any performance cost this would cause on the current API, that would probably be the best way to move this forward. |
@mikeal what would 'ok' look like? The V8 team is starting some promise optimisation work, so performance is only going to improve there. If we can introduce promises with negligible impact to using callbacks, that feels like a ✔️ to me. If using promises turns out to be significantly slower than callbacks, that's something we can give to the v8 team to fix. |
Let me clarify. If this new promise API is slower than the callback API, I don't think that's a huge deal. The bigger dealbreaker is if there is any performance impact on the existing callback API by adding support for promises. Node.js has a general policy that things do not get slower from release to release but there's no real policy about new APIs being equal in performance to prior APIs. |
Ultimately, if the user of an API is concerned about performance, they can proceed to use callbacks instead of promises - the existing callback support is not going to be removed because it would break stable APIs. The biggest problem I see is preventing confliction with APIs which assume synchronousness when a callback is not provided. For example: sync & async: https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback Most APIs I've seen use this convention though, which would be compatible without caveats: |
it's just a breaking change... with semver, that's not a very big deal |
@madbence Node has very strict API stability rules, which I believe persist across major versions because it's hard for package maintainers to check engine versions - https://gist.github.com/isaacs/1776425 |
For APIs that would be difficult or impossible to update without breaking compatibility, would it be possible to create new methods that use promises? |
I don't think introducing promises necessarily has to be for every API endpoint ever. At first, and for a long time, maybe forever, it could just be for the most commonly used APIs, such as |
@calvinf We could add new methods like |
It's primarily going to be for |
It's worth mentioning that v8 exposes internal hooks for promisification. There are two conflicting time constraints here:
(Please, it has been shown time after time that "promises are slow" or "callbacks are closer to the metal" is FUD, multiple languages use promises as their core abstraction for asynchronous actions). |
Be careful with statements like that, as a considerate amount of people (including me) prefer callbacks over coroutine style declarations. I'm suspecting it's a matter of taste, and blanket statements like that can easily polarize discussions. I think it's best to keep this discussion pointing towards how promises could be implemented into the Node API and what the results of that would be. |
Seconded. Also, this is more than a matter of preference, there's thousands of modules in npm that rely on the existing callback API. I don't see a future where we can ship a version of Node.js where all of them break. |
The existing callback APIs are clearly not going to be removed for the foreseeable future. Please stop raising concerns around this - it simply isn't going to happen. |
Yo, can we lock this thread? It's been closed for a while and over 30 people are now being spammed for something months after discussion had ended. Thanks! edit: just realized this is the second time I'm calling for this. cc/ @jasnell think it's time |
I'm fine with locking this if we provide users with an alternative place to ask their questions. Giving people a locked thread and nowhere to turn is not where I think we want to be in terms of community openness. |
@benjamingr don't know if this has been considered but one way to mitigate forward compatibility issues with older code bases, would be to bundle the introduction of potentially breaking changes in the promise based APIs with import/export module loading (e.g. |
@olalonde yes, it has been considered several times as well as several other alternatives. Thanks for the suggestion though. See nodejs/node#5020 if you're interested in the previous discussion. |
can i ask you to point to any discussion's result? |
@iamstarkov pretty much #25 (comment) - it's a lot of work, very smart and diligent people have tried and so far their efforts are stagnating (see nodejs/node#5020) You can also support nodejs/CTC#12 |
@yoshuawuyts Can I ask why it would need to be locked -- can't people just unsubscribe if they don't want notifications? |
even |
@benjamingr It appears that you can debug async stack traces using the inspector protocol. See the blog entry for VS Code version 1.11 for more info. Is this what you were waiting for? |
@styfle real async stack traces are very different from what you have in V8 at the moment, you can't - for example - go to an earlier frame and inspect its local stack variables - you can just see where it came from. |
@benjamingr |
No, but non-promise Node throws synchronously on errors where promise node waits for the next micro-task. |
@pleerock totally agree. Are we going to have 10 years of "callback style" Node core API while the world and ecosystem is fully async/promise based? :( |
Fwi, here are some current relevant discussions that are going on:
No. |
to keep existing callback based API act as before, but add promisify support without any extra import/syntax, maybe this is good. const fs = require('fs');
fs.readFile('file', (err, fileConent) => {} ); // for callback based API
fs.readFile('file', 0).then(...).catch(...); // for promise based API if the original callback parameter is not a function, but a false value, it mean a promise will be returned. advantages
if a callback-base API's last parameter before callback function is optional, it should be a option object, an object is a true value. @billinghamj or the return-promise flag in replace of callback function can be a special value or an special global object ( maybe is global.Promise ) |
@kaven276 I imagine that could conflict with various APIs which have optional parameters. Booleans and/or numbers may already have another meaning if put in the place of a callback param. There are a very large number of core APIs and their interfaces are very varied, so the solution must be guaranteed to work in all cases. |
nodejs/node#12442 landed 2 weeks ago. It's available in the nightly builds and should be available in the final Node 8.0.0 release. I wrote about it on medium. This gets us really close to having Promises in core and I suggest everyone play around with it now if you haven't already to see how it may (or may not) solve your problem. |
Hmm it is a good step, but it's just so little and so late. If it allowed e.g. |
-1 and then what next? are we going to add generators and async/await design-patterns as well? i don't want node-core's api and design-practices to turn into chaos, like what's currently going on in frontend-development world since es6/babel was introduced to it. |
@kaizhu256 what's unstable about promises? According to surveys the vast majority of Node.js users already use them anyway - and at the moment there is a hassle involved in order to use the language. |
@benjamingr, you are then encouraging people to try and promisify sockets, just because they can (and request and response objects). can you imagine the never-ending code-refactoring/debugging this will cause and tie up resources to actually shipping a product? |
i would say joyent's stable stewardship of nodejs was a good thing, which allowed people to build incredible things, without worrying about node's api constantly changing. you guys risk turning nodejs into a joke (like what's going on with npm after they mucked up npm-install and npm-publish). |
afaik backward-compatibility is taken very seriously in node-core, that's why |
yes, but stable isn't the only criteria about node-core's design patterns. They also must be a good design-patterns. But we all know that "stable design-patterns" you are talking about are callbacks which produce a callback hell and unmaintainable code, we all know about. This means that such "stable design-patterns" are actually anti-patterns. Does node-core need stable design-anti-patterns? |
@kaizhu256 promisifying sockets would never happen since promises are for one time things. The goal is to provide people with the most convenient API. If you look at the prior art - absolutely no one is suggesting breaking APIs. I do however have every intent to pursue async iterator support in core once those land in V8 and we have already been doing work for it. Note that stability isn't being sacrificed here. |
@pleerock let's please not turn this into a "promises vs. callbacks" debate or discussion. I am interested in engaging and discussing with @kaizhu256 because his point of view is important to me and I am happy they chose to engage with us. I do not want to belittle their experience or use case or to assume mine is more valid than theirs. I would like to convey that Node.js is committed to API stability and to discuss how they feel adding support for promises might impact that. Thanks :) |
@benjamingr i liked joyent's vision that all builtin modules should work towards reaching eventual frozen status, for the sake of api stability. people like me would like nothing better than to see fs, net, etc. eventually get to frozen status. i would be against anyone deciding to revisit existing builtins to tack on promise or generator apis to them (there's fear these things will end up rewriting the entire module and break stability). i'm ok with people creating new builtins like fs2 or fsPromise, but leave the existing modules alone. |
I couldn't find any discussion related to that and I've been told core contributors aren't a fan of promises but I thought I'd kick start a discussion anyway. I had assumed this was discussed at length already but couldn't find any relevant links.
The proposal would be to make all async methods of the core library return promises. This could be done in a backwards compatible way by using something like bluebird's nodeify (now asCallback) which would allow both errback / promise styles to co-exist.
The text was updated successfully, but these errors were encountered: