ECMAScript proposal, specs, tests, and reference implementation for Asynchronous locks
This initial proposal was drafted by @johnhenry with input from @_ and @_.
Designated TC39 reviewers: @_ @_
- Table of Contents
- Status
- Prior Art
- Rationale and Motivation
- Proposed Solution: Asynchronous Blocks
- other operators
- for/for-in/for-of
- for-await-fo
- while
- do
- other operators
- Specification
- Transformation
This proposal is currently at stage 0 of the process.
Some of the syntaxes in this proposal, namely the "do await" syntax, have been discussed before
Previously, in order to preform a one-off task within a program that didn't affect the global scope, one had to use an Immediately Invoked Function Expression (IIFE).
(function(){
const a = 1;
console.log(a);
//other stuff...
})();
(function(){
const a = 2;
console.log(a);
//other stuff...
})();
//logs "1", "2";
With blocks and blocked scoped variables, introduced for 2015, the syntax becomes much cleaner.
{
const a = 1;
console.log(a);
//other stuff
}
{
const a = 2;
console.log(a);
//other stuff
}
console.log(a);
//logs "1", "2";
Currently if we want to invoke as asynchronous expression, set to be introduced for 2017 we must resort to using an IIFE once more.
(async function(){
const a = await eventually(1);
//other stuff...
})();
(async function(){
const a = await eventually(2);
//other stuff...
})();
//logs eventually "1", "2" or "2", "1"
Alternatively, we can use non-anoymous functions and invoke them later in the program:
const main = async function(){
const a = await eventually(1);
//other stuff...
};
const main2 = async function(){
const a = await eventually(2);
//other stuff...
}
main();
main2();
//logs eventually "1", "2" or "2", "1"
but this is still not as elegant as one might hope.
We can make this cleaner by implementing asynchronous blocks
async {
const a = await eventually(1);
console.log(a);
//other stuff
}
async {
const a = await eventually(2);
console.log(a);
//other stuff
}
//logs eventually "1", "2" or "2", "1"
Asynchronous blocks are executed asynchronously with respect to the surrounding context and allow use of the "await" keyword without having to create another functional scope.
This would work in conjunctions with other operators that come before blocks:
const for(const i of [1, 2]) async {
await eventually(i);
}
console.log(0);
//logs "0"
//logs eventually "1", "2"
See Asynchronous Iterator Proposal
async {
for await(const i of eventuallyIterator(1, 2)) async{
console.log(i);
}
console.log(0);
}
//logs "0"
//logs eventually "1", "2"
while(true) async {
await eventually(i++);
}
console.log(0);
//logs "0"
//logs eventually "1", "2"...
const result = do async{
const a = 1;
}
result.then(console.log.bind(console));
//logs eventually "1"
When Number.prototype[Symbol.iterator] is called, the following steps are taken:
- While parsing, if the keyword "async" is encountered directly before a block, execute that block's contents asynchronously with respect to the surrounding program.
- That block will now have the "await" keyword available at the top level.
- Declarations using keywords "let" and "const" would not affect the scope outside of the asynchronous block.
- Keeping in mind backward compatibility, declarations using the keyword "var", along with naked declarations, would affect the scope outside of the asynchronous block.
The following transformation recursively on a string:
const match = /^async\s*{\s*(.*)\s*}/m;
const wrap = (code) => `(async function(){${code}})()`;
export default (code)=>{
return match.test(code) ? wrap(code.match(match)[1]) : code;
};
would have the following result:
async { ... } -> (async function(){ ... })()
Although, it misses the requirement regarding "backward compatibility" above in that it does not respect the use of the "var" keyword.