nodejs/NG

Provide promise based API for core libraries

olalonde opened this issue Β· 87 comments

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.

Trott commented

Here's one place it's been discussed: nodejs/node#11

But that issue is closed pending future discussion. So this is future discussion 😊

Trott commented

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:

First, promises are not as bad as you think and there are some simple, effective ways to organize callback driven code to avoid common complaints.

Second, callbacks have costs and benefits like anything, and while the benefits are well-advertised, the costs can be hidden and frustrating.

Third, the callback is the fundamental unit of asynchronous programming in JavaScript and Promises are just callbacks with added semantics (a specific type of callback if you will). There is no single abstraction that's perfect for all use cases.

Fourth, promises aren't a silver bulllet. There are whole classes of use cases where promises aren't the right abstraction [...].

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.

Fifth, the design philosophy of node core is to encourage maximum compatibility and discourage decisions that lead to incompatibility. While it is trivial to turn a promise based API into a callback one, the opposite is not always true.

Sixth, integrating with c++ bindings/libuv has only been possible using callbacks. The core team is committed to keeping node lightweight and fast by providing APIs that are as "close to the action" as possible, so they're not going to be introducing a new abstraction to asynchronous programming in core anytime soon: http://blog.trevnorris.com/2014/02/nodejs-es6-and-me.html

In my mind, this is the only substantial argument against promises in Node. From the linked blog post:

When the v8 team introduces an API for generators or promises I will give it honest consideration.

So I guess the question is, has v8 introduced this yet? If not, I'll be happy to shut up until then :)

Qard commented

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?

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 fs module as optional, they are required.

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 error handlers are present, I'd have thrown errors bubble out of the promise if no errback handlers were registered (again, when no callback is passed).

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.

@mikeal Afaik those are still slow, and it's v8 to blame β€” e.g. Bluebird is very fast. Perhaps this has to be changed on the v8 side, and then the speed argument would be much weaker.

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 fs.readFile.

@calvinf We could add new methods like crypto.randomBytesAsync, but that's not super ideal in terms of keeping a consistent API. Probably a worthwhile tradeoff though.

It's primarily going to be for fs, dns, child_process.exec, etc. Events and streams are not thenable patterns, no one is proposing doing that right?

One of the main objections to this has been performance.

It's worth mentioning that v8 exposes internal hooks for promisification.

There are two conflicting time constraints here:

  • V8 promises are still pretty slow, at least much slower than userland solutions.
  • When async/await lands (it already landed in Chakra, for example) our callback APIs would become mostly irrelevant anyway.

(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).

our callback APIs would become mostly irrelevant anyway

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.

Be careful with statements like that, as a considerate amount of people (including me) prefer callbacks over coroutine style declarations.

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.

Theres no way promises are more performatic than callbacks, but the idea just return a promise if no callback is provided is pretty good! When we good a way to transform the callback style in promise style is good for now, but maybe a function "Async" in the API can help us to deal with this at now.

const fs = require('fs');

fs.readFileAsync(...).then(...);

We have the readFIleSync, before introducing internally readFile return a promise if no callback is provided, we can use readFileAsync, after all, we can just put readFileAsync an alias for readFile.

@thebergamo very nice! πŸ‘

We can use something like this too, I guess:

const fs = require('fs').promise;
fs.readFile('foo')
  .then(() => {})
  .catch(() => {});
rvagg commented

const fs = require('fs').promise begs the question why you couldn't just do npm install fs-promise (hypothetical) and const fs = require('fs-promise')? Then it could benefit from increased choice and competition in userland and the ability to adapt and improve over time much more quickly than if it gets stuck in core where change is cumbersome. There's so much in core right now that would work better if it was in userland.

As was once said, a standard library is where code goes to die, so be careful what you wish for. Just look at streams3 for the kind of pain involved. Review this discussion: nodejs/readable-stream#181

totally coming out of left field here. Would it perhaps be sufficient to ship a method in util that will wrap any node callback style function as a promise?

var fs = require('fs');
var util = require('util');

var read = util.promisify(fs.read);

This functionality is present in many places of userland (q, bluebird, etc). Perhaps simply having something like that in core and fully documented will allow people to instrument promises without requiring that much extra code or relying on user land implementations

@thealphanerd This might actually be the most elegant solution, as it blesses a well established pattern, provides scoping for the promise API (no conflicts with sync returns), doesn't further complicate internals and doesn't cause any possible perf regressions. I like it.

@rvagg I like the rust ideology that anything which harms user land to be fragmented on should be in the standard library. Continuing the rust example, Option is included in the standard library because it would be harmful to have 5 different Options in user land as code boundaries start to get really messy.

It is harmful to have 5 different async/promise implementations in user land for the same reason. TC39 obviously agrees or else promises wouldn't be in the JavaScript standard library.

The require('fs').promise and utils.promisfy(...) approach won't play nice with the next hot ES6 feature. Modules.

+1 to invisible promise support via callback omission.

rvagg commented

My assertion is that the opposite is true, there is a value in fragmentation that comes from competition, the creative destruction that comes from having to provide users with the best possible version of some idea. You can see that in this particular ecosystem with the competition between q and bluebird and the relative popularity based largely on two factors: features and performance. We see the same effect happen on a larger scale with browsers and JavaScript runtimes. Chrome was born into a semi-competitive landscape and turned the discussion into performance above all, and they arguably won that argument and the others have converged somewhat in the same place. JavaScript runtime authors are now seeing that users are scrambling for features, the popularity of Babel is testament to that. So the conversation has completely switched and they are measuring themselves based on number-of-features-implemented. Just think of IE dominance as the opposite to this situation, because that's exactly what that was.

As soon as you take a competitive ecosystem and turn it into a monomorphic one you lose all of those benefits and end up having to campaign for your preferences via other means. In the case of core, if we stick all of the Promises functionality into core then everyone who relies on them would have to come and make a case for things being a certain way and it would largely come down to argumentation skill and politicking. Just look at how difficult it has been to agree on unhandledRejection in the first place and now trying to change the default behaviour is yet another level of difficulty. Libraries like q and bluebird can do that unanimously and will soon discover whether their users are happy with their choices. If you give core the decision-making power over you favourite thing, then you're stuck with having to put up with whatever core wants to feed you based on all of the personalities, opinions, regard for stability and backward-compatibility compromises, etc. that go into that process.

Even a util.promisify() has this same problem, it'll do things a certain way and there will be endless discussion whenever someone wants it to do something different. Right now you can already choose from multiple implementations in npm that do things according to your preference.

So, my question remains: while we still exist in a space where Promise is not required in order to make maximum utility of certain language features (a situation that seems like it won't remain into the future, although we have no idea what timeframe that is yet), what is the actual benefit of putting it into core beyond saving you an npm install? Because unless you can come up with some really compelling reasons, you're coming up against the negatives of having a rigid, monomorphic implementation that is likely to be very please very few people 100%.

Rather than add API surface to core, those wanting to use 'fs' via promises could always change

var fs = require('fs');

to

var fs = require('fs-promise');

from here https://www.npmjs.com/package/fs-promise

Edit: Whoops didn't see rod already said this.

@rvagg I agree with you like 90%. Diversity and competition are great things, most of the time. When diversity hurts people building new things, that's a problem. Say I'm developing a new library which I want to use promises my users will consume, what do I install? q or bluebird? Do I make it optional? Do I support both? Do I roll my own implementation like jQuery?

Asynchrity is a first order problem in the β€œsingle threaded” JavaScript, it requires a first order solution. Errbacks are a contract, not a primitive. Promises can be a primitive.

Ultimately on this issue, I don't place my trust in this thread. I place my trust in TC39 and the browser vendors who pushed to make the promise a language primitive. The problem has been solved. The solution is in production. The primitive is being used in new standards (take a look at the fetch API). Now it's just a matter of waiting for node to grow into the promise.

So what is the benefit of Promises in core? The entire community has a single elegant way to talk to each other about asynchronous data without worrying about what contract it supposedly upholds.

Edit: When you need something like this fragmentation has gone too far.

I place my trust in TC39

I place my trust in @rvagg and the rest of Node core, who have kept the Node API lean and fast since its inception. If they express an opinion about maintainability and performance, we all ought to take notice.

@calebmer Simply because TC39 and browser vendors have exposed a promise API does not imply it is the single elegant way. Callback-based APIs are not going to universally go away now that promises are part of the ECMAScript standard. In exposing a promise API, TC39 did not deprecate callbacks at the same time. As is the case now, some people will use promises and others will not.

You are free to enhance Node core in whatever way you want. And you are free to wrap any and all Node APIs in promises. Nothing is stopping you as @rvagg said from running npm install fs-promise. The reality is that much of Node core could be packaged as userland modules which can be installed as needed; e.g., as should happen with utils, url, path, etc. There are significant advantages to keeping Node core as small as possible, which far outweigh the costs associated with expanding APIs to accommodate every individual proclivity. In this case, Node has a callback API. If you want to decorate it in whatever fashion suits your fancy, that is your responsibility, not Node core's.

Re: your example and q or bluebird. Choose which ever implementation you prefer. End of story. If users don't like your choice, that is on them. If q or bluebird have API differences, there are probably reasons for those differences, differences which you can either agree with or not.

Further, I sense that you hold that, since TC39 has included a promise API, there is nothing more to say about the issue. Simply because a promise API exists does not preclude the need for other promise implementations. Other implementations might include performance gains or enhanced features or whatever. By leaving promisification to users, you will be free to take advantage of those "enhanced" alternatives by adding/swapping dependencies.

And lastly, simply because TC39 includes a feature does not mean that Node core must embrace said feature across its entire code base. Node core is its own entity which can pick and choose what APIs/features it wants to embrace just as you, a library author, can do when you create a library. In this case, backwards compatibility, small surface area, and stability far outweigh the need to oblige promise enthusiasts. Node core should be kept as small as possible for the benefit of everyone involved.

Here is the problem. If I want fs, which of these should I use?

A given project may end up containing all of these libraries in the same project. I'd much prefer to just have core supply a single implementation that everyone uses, especially for something as basic as fs.

@rvagg's points about the benefits of userland evolution vs. core stagnation are reasonable. But they are a reason to move fs out of core, and find a way for the core team to sanction and maintain certain userland modules (#9). They are not a reason to relegate promise-based fs APIs to userland.

I apologize to @rvagg, I wasn't aware of your previous work and history with the node foundation when writing my response. The work you and others do for node and our community I hold with the upmost respect and gratitude.

I agree with moving things like path, url, and http to user land while still (hopefully) being maintained by the node foundation. But I also still stand in my belief that Promise is a JavaScript async primitive and should be treated by node as such for the β€œfatigued” end user who just wants to write idiomatic async code which doesn't unwravel into callback hell (eventually this will just be async/await).

I place my trust in @rvagg and the rest of Node core, who have kept the Node API lean and fast since its inception. If they express an opinion about maintainability and performance, we all ought to take notice.

Please let's not use arguments like this. This is the logical equivalent of ad hominem. The opinions should stand on their own merit and if they don't, then they are bad regardless of who says them.

util.promisify()

YES! +1 for adding it. It's a great (& simple) start. Promise support goes pretty far back now. Node gave us error first callback style... I think it's reasonable to provide a simple bridge to Promise style also. It could be as simple as this...

const promisify = (ctx, name)=> {
  var fn = ctx[name] || ctx;

  return function promisified() {
    var l = arguments.length;
    var args = new Array(l+1);
    for(var i=0; i<l; i++)
      args[i] = arguments[i];

    return new Promise((resolve, reject)=>{
      args[l] = (error, data)=> error? reject(error) : resolve(data);
      fn.apply(ctx, args);
    });
  }
};

var fs = require('fs');
promisify(fs.stat)('package.json')
.then(console.log)
.catch(console.error);

EDIT: changed code to a slightly more complicated example that optimizes (without TurboFan)

Yeah- we'll debate the performance and validity of whatever is used. Perhaps people will want something with a tad bit more functionality... but I'd rather have a 'little of something' instead of 'all of nothing' ;)

Userland is awesome... but....

I like what @domenic said earlier. I go out of my way to write modules with NO dependencies and I also place a high priority on using modules that have little-to-no dependencies... and not because I just like to be tidy! ††

I have absolutely no issue using fs-promise (or similar) in code that isn't for publishing on npm... but I would never use it in something that I do intend to publish. This means I either need to include a promisify snippet like above in the module, or just use error first callback style.

One reoccurring comment I've noticed on this topic is:

You can just as easily use require('fs-promise')

I think that argument is possibly relevant to people writing apps (unpublished things). Then, in that context, we get:

There are significant advantages to keeping Node core as small as possible

Yup. Certainly true, but... I think it is unfair to use it in a context that implies that userland authors don't ALSO want to have their modules "as small as possible."

For the record: fs-promise has a dependency on any-promise and, now, fs-extra... which has a dependency on... rimraf, path-is-absolute, klaw, jsonfile, and graceful-fs... and then it goes on....
fs-promise dependency tree
That is just too much for getting a then() off of fs.stat()!

†† When I was at Tinder, I had to produce a weekly report of ALL modules used and their licenses. I've spoken to others that are required to do the same. Apparently this is common.

Bake it in?

Many libs are now returning Promises when the callback is left off (eg: stripe, mongodb). Some even return them when the callback is there! Both of these add SOME overhead: Either by argument sniffing or by instantiating a Promise. So, as @mikeal said, that overhead must be offset by something so that performance still shows as a win overall.

Adding require('fs').promise to core is a good suggestion, but it's like adding it as second class citizen... which, by definition, isn't "core" then. It allows any performance hit to be opt-in... however it seems silly to litter a bunch of core files with opt-in stuff when we could just opt-in via util.promisify() (assuming the littering isn't able to provide some kind of performance gain).

I created a module which promisifies functions or objects of functions like fs.
The special thing is that it pre-promisifies the functions during the import. So no loss when a function is actually called.

It behaves normal if a callback function is provided, if not it returns a promise.
I ran a benchmark and it seems that while the promise return is slower, the callback return is not slower than the native callback now.
I would be glad if others could run that test too.
So providing both in one could very well be used as a solution in the core library.

What do you think?

bildschirmfoto 2016-05-27 um 15 48 47

@petkaantonov I think the logical error you're referring to is "appeal to authority". That is, just because @rvagg has years of experience building the core of a technical system (you seem to be saying) this in no way makes his opinion superior to someone who has had nowhere near the same experience or deep technical knowledge.

An ad-hominem attack would be "Of course @petkaantonov, the author of Bluebird, wants to see Promises everywhere! We should dismiss his views since he cannot remain objective".

Obviously these are both wrong in some way.

this in no way makes his opinion superior to someone who has had nowhere near the same experience or deep technical knowledge.

You're critiquing a logical error by using a strawman? o_O And you then offered a direct ad hominem attack on an individual as if it were purely example? Of all the wrong errors, this would probably be the wrongiest.

@matthew-dean I'm not critiquing, I think everyone is awesome. For instance, in my last book and in other writings I praise P. Antonov by name for his excellent work w/ Bluebird and performance studies. Vagg is an equal giant.

These are both examples, in context. Not sure why that's hard for you to see. The last line should give it away. Hopefully this clears it up.

Lots of great discussion regarding promises!

I like the idea of returning a promise if there is no callback parameter, but there are a couple issue that it brings up:

  1. It might be confusing for new node.js users to understand. Which should be used? Can we mix and match? (not a very big deal)
  2. It could also break existing tooling that expects a certain return type. TypeScript could update lib.d.ts to handle this but I don't know about Flow.
  3. It could degrade performance for someone who is not providing a callback function because they don't care about the response. This would return a promise when they don't even need it. I don't know how often this happens is the wild.

I agree with most of @williamkapke points above...require('fs-promise') is not a good solution. I actually prefer wrapping the native module with my own rather than pulling in another 10 dependencies.

import * as fs from 'fs';

export function writeFile(name: string, data: string) {
    return new Promise((resolve, reject) => {
        fs.writeFile(name, data, (err) => {
            if (err) {
                reject(new Error('Failed to write file to disk: ' + err.message));
            } else {
                resolve();
            }
        });
    });
}

I would like to see more modules on npm returning promises instead of using callback style and I don't think that will happen until we see it land in core someway or another.

I agree with your stated issues. However:

  1. As you say yourself its not a big deal for newcomers. But the goal over long time is i think to stop using callbacks for normal cases (especially with the new async functions that hopefully will make it to a soon release.
  2. If the function is async, why should it return any value before its complete?
  3. I think any async function, success or failure should be handled in any case. Hopefully that will make people more aware of that circumstance.
    If no promise should be returned, maybe there is a possibility to return only an object with then/catch properties when called return the real promise.

I think there should be one universal solution to guide developers and create a standard every module can use.

What do you think?

+100 for promise-based APIs.

With Promise in ES2015, fetch making it's way into browsers, and async/await on the horizon it's pretty clear promises are the future of asynchrony in JavaScript.

Promises are a better abstraction than callbacks in every way, except historically performance, which is likely just an optimization problem. They are more composable and predictable, and propagate errors like people are used to with exceptions.

And if you still prefer callbacks for whatever reason, drop a couple extra parens after a function call that returns a promise and you've got your damn callbacks!

Fun fact: Node v0.1 actually had a (strange) form of promises.

Heya, can we lock down this thread? There's https://github.com/nodejs/promises now, and this thread feels like it's reached zombie status. Thanks! πŸ‘‹

Good idea. Will close the thread in favor of discussions in the new repo. If communication continues here I'll lock the thread. Thanks all!

As a data point, ~75% of Node.js developers now use promises for async control flow according to this survey:

node-js-survey-async-control-flow-promises

Did the discussion move somewhere else?

Also worth noting that async/await is promises. How authoritative is that survey though? 1126 respondents isn't that many.

Also no copy of the survey is provided, nor is it discussed how the survey was conducted or where it was advertised.

Link to the full survey results: https://blog.risingstack.com/node-js-developer-survey-results-2016/

While I agree promises are very widely used, and are important for Node to support in the core modules, I wouldn't lend much weight to this data.

I think we already know promises "have won" and are more popular than callbacks in the wild at this point. That survey shouldn't surprise anyone and there are numerous other surveys that show similar results.

I'm just not sure why any of that is relevant to this discussion. There is already consensus among core that promise APIs are a swell idea overall (alongside and not instead of the callback ones). There are just a lot of things that need to happen first and it's a huge huge undertaking to work on - that's the cause of the stagnation mostly.

If I had to guesstimate - it'll get postponed until V8 JS gets C# like async stack traces which would solve the postmortem concerns some people have.

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. const fs = require('fs') would return a completely backward compatible API while import fs from 'fs' could return a promise based API which might otherwise potentially break in older code bases).

@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.

@yoshuawuyts

discussion had ended

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

jmm commented

@yoshuawuyts Can I ask why it would need to be locked -- can't people just unsubscribe if they don't want notifications?

even async/await landed to node and its still not supported? Noone using callbacks these days, everybody are using promises.

If I had to guesstimate - it'll get postponed until V8 JS gets C# like async stack traces which would solve the postmortem concerns some people have.

@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 you can't - for example - go to an earlier frame and inspect its local stack variables I don't understand... is this currently possible with non promise code?

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:

Are we going to have 10 years of "callback style" Node core API while the world and ecosystem is fully async/promise based? :(

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

  • API for promisify support is simple
  • callback code and promise code is similar
  • if you are used to callback, switch to use promise is very simple
  • 0 can be used as false, so code is short for return promise
  • existing code will not broken

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 )
fs.readFile('file', Promise) will return Promise cause the last parameter is Promise itself.
API implementation will check the last augument if === Promise; @billinghamj

@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. require('util').promisify(require('fs')) then it'd be useful

-1
adding promises to node-core is a terrible idea. node-core should have stable design-patterns. adding promises will lead to contributors needlessly refactoring node-core with promise design-patterns (like what happened with let and const).

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 util.promisify was introduced in node@8 instead of changing the existing api. promises are already part of the core.

node-core should have stable design-patterns.

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.