Symbol.await proposal

# James M Snell (4 years ago)

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.

# Wesley Oliver (4 years ago)
# Jamie (4 years ago)

One of the common refactorings I do is:

let file1 = await readFile(filename1) let file2 = await readFile(filename2) // to let [file1, file2] = await Promise.all([ readFile(filename1), readFile(filename2), ])

I would be very confused if refactoring it in this way made my code break because of some implicit behavior around await specifically.

I have seen some APIs that switch to promises when a callback argument is not provided. Is this not a sufficient solution?

# Wesley Oliver (4 years ago)

The one interesting case you have to look at dealing with, is the one I came across where by the call of a function can return a results and will then later async call the callback in the future. Because with the proposal above, you have lost the ability to, return the return result and callback results you have return {execReturn:..., awaitResult:}, this doesn't happen offen.

I think one approach that you could take but wouldn't always work, check for additional the call back parameter, then either return Promisification or you do the call back execution. a wrapper would help with that.

The other could also look at a special import syntax for modules, whereby methods are automatically promisified, with this wrap approach or shallow copy.

The problem is that for a function that can accept in javascript any number of parameters, arguments, were is no finite number of arguments, you can't be certain that there is a callback or if the last parameter is indeed a param. One could do type check on the last parameter, otherwise you have a naming convention for functions, to avoid the confusion, provided you could check the key(name) Promise all suffers the same problem with the last parameter, it is a convention.

Kind ,

Wesley Oliver

# Wesley Oliver (4 years ago)