Symbol.await Proposal

For many legacy code bases that are based on callbacks mechanisms like node.js' promisify function are required to help facilitate the transition from callback centric code to Promise-centric. A lot of the time, these can follow straightforward rules without requiring customization. However, at other times it is necessary for user code to provide custom implementations of the Promise-version of the function.

In Node.js, we accomplish this by allowing a function to have a symbol attached whose value is an alternative function that is returned by the promisify function

For instance,

function myFunction(foo, bar, callback) {

callback(null, foo, bar);

}

myFunction[util.customPromisifySymbol] = async function(foo, bar) {

return [foo, bar];

}

const { promisify } = require('util');

const mine = promisify(myFunction);

(async () => console.log(await mine('a','b')))();

As a convenience built into the language, it would be nice to be able to short-circuit the need to call promisify with a special language-level Symbol used specifically for this purpose:

function myFunction(foo, bar, callback) {

callback(null, foo, bar);

}

myFunction[Symbol.await] = async function(foo, bar) {

return [foo, bar];

}

(async () => console.log(await myFunction('a','b')))();

The idea here is that if the function being awaited has the [Symbol.await] property whose value is a function, then that function is called when the await keyword is used. That is,

myFunction('a', 'b', callback); // Invokes myFunction directly

await myFunction('a', 'b'); // Invokes myFunction[Symbol.await]

if the Symbol.await property is not set or is not callable, then it would fallback to default behavior.

Automatic handling of this binding should also happen but has some technical detail to work out:

const obj = {

a: 1,

foo() {}

};

obj.foo[Symbol.await] = async function() {

return this.a;

}

await obj.foo(); // Calls await obj.foo[Symbol.await] with bound this

This approach would make it far easier for legacy code bases to make the transition to async/await syntax while maintaining legacy compat.

Before writing up a formal proposal, I wanted to solicit some feedback on this approach to see what folks thought.

(my apologies for cross-posting this also to the es-discuss mailing list... I posted it there first then received a suggestion to post here also)

Since await is sugar for a nested .then, i would not expect a Symbol protocol that was only usable via await. Separately, if the function can provide its own implementation, why wouldn’t it provide a promise-returning version instead?

Hm. I get the motivation for this, but once added we almost certainly would never be able to remove it. I'm not sure that the utility it would have in allowing more seamless upgrades for legacy codebases would be worth the cost of having it in the language forever.

FWIW a pattern I've used is to have a .p property on functions themselves, which is somewhat more convenient to use - instead of await promisify(foo)(args) you can do await foo.p(args).