📜 ⬆️ ⬇️

Simplify asynchronous JavaScript code using asynchronous functions from ES2016

Although we are still working on the introduction of support for ES6 / 2015 , the Chackra team is also looking beyond ES2016 and, in particular, for asynchronous functions . We are pleased to announce experimental support for async functions in Microsoft Edge, starting with the Microsoft Edge assembly (EdgeHTML 13.10547) .



Asynchronous functions in ES7 / ES2016


The keywords async and await , as part of the proposal for implementing asynchronous functions, are aimed at simplifying the writing of asynchronous code. This is one of the key features of modern C # and a frequently requested option from JavaScript developers . Before the introduction of asynchronous functions and promises, the JS developer had to wrap all the asynchronous code in separate functions from the synchronous code and use callback functions to work with the result of the asynchronous calculation. Such code quickly becomes difficult to read and maintain.

Promises from ES, going through standardization and getting more and more direct support in browsers, helped to improve the way of writing asynchronous code, but did not solve the problem completely, since the need to write callback functions has not disappeared.
')
Asynchronous functions are based on promises and allow you to take the next step. When you add the async keyword to a function or an arrow function, it will automatically return a promise. For example, the following code is a typical ES2015 program that makes an http request using promises:

// ES6 code, without async/await function httpGet(url) { return new Promise(function (resolve, reject) { // do the usual Http request var request = new XMLHttpRequest(); request.open('GET', url); request.onload = function () { if (request.status == 200) { resolve(request.response); } else { reject(Error(request.statusText)); } }; request.onerror = function () { reject(Error('Network Error')); }; request.send(); }); } function httpGetJson(url) { return new Promise(function (resolve, reject) { // check if the URL looks like a JSON file and call httpGet. var regex = /\.(json)$/i; if (regex.test(url)) { // call the promise, wait for the result resolve(httpGet(url).then(function (response) { return response; }, function (error) { reject(error); })); } else { reject(Error('Bad File Format')); } }); } httpGetJson('file.json').then(function (response) { console.log(response); }).catch(function (error) { console.log(error); }); 


If we rewrite the same code, preserving its behavior using asynchronous functions, the result will be more compact and easier to read. The code below also includes a small refactoring in the error handling section (note the httpGetJson function):

 // ES7 code, with async/await function httpGet(url) { return new Promise(function (resolve, reject) { // do the usual Http request let request = new XMLHttpRequest(); request.open('GET', url); request.onload = function () { if (request.status == 200) { resolve(request.response); } else { reject(Error(request.statusText)); } }; request.onerror = function () { reject(Error('Network Error')); }; request.send(); }); } async function httpGetJson(url) { // check if the URL looks like a JSON file and call httpGet. let regex = /\.(json)$/i; if (regex.test(url)) { // call the async function, wait for the result return await httpGet(url); } else { throw Error('Bad Url Format'); } } httpGetJson('file.json').then(function (response) { console.log(response); }).catch(function (error) { console.log(error); }); 


The async keyword also works with ES6 arrow functions; it ’s enough just to add a keyword in front of the arguments. Here is a small example:

 // call the async function, wait for the result let func = async () => await httpGet(url); return await func(); 


Total:


How is this implemented in Chakra?


In a previous article, we discussed the architecture of the Chakra engine using the diagram below. The parts that required the greatest changes to support asynchronous functions are marked in green.



Global behavior


Using the async keyword generates a promise constructor, which, according to the specification, is a wrapper around the contents of the function. To perform this action, the Chakra bytecode generator makes a call to a built-in function that implements the following behavior :

 function spawn(genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self); function step(nextF) { var next; try { next = nextF(); } catch (e) { // finished with failure, reject the promise reject(e); return; } if (next.done) { // finished with success, resolve the promise resolve(next.value); return; } // not finished, chain off the yielded promise and `step` again Promise.resolve(next.value).then(function (v) { step(function () { return gen.next(v); }); }, function (e) { step(function () { return gen.throw(e); }); }); } step(function () { return gen.next(undefined); }); }); } 


The generating function above is designed to process all async expressions in the function body and decide whether to continue or stop the process depending on the behavior inside the async function. If an async expression invoked with the await keyword fails, for example, due to an error occurring within the async function or the expected expression in general, the promise returns a fault that can be processed higher in the stack.

Next, the engine must call the generating function from the JS script to get the promise and execute the contents of the function. To do this, when the parser finds the async keyword, the engine modifies the AST (abstract syntax tree) representing the algorithm to add a call to the spawn function with the body of the objective function. As a result, the httpGetJson function from the example above is converted by the parser as follows:

 function httpGetJson(url) { return spawn(function* () { // check if the URL looks like a JSON file and call httpGet. var regex = /\.(json)$/i; if (regex.test(url)) { // call the async function, wait for the result return yield httpGet(url); } else { throw Error('Bad Url Format'); } }, this); } 


Note the use of generators and the yield keyword to implement the behavior of the await keyword. In fact, implementing the await keyword support is very similar to working with the yield keyword.

Behavior with a default argument


One of the new features in ES6 is setting the default value for the function argument. When the default value is used, the bytecode generator will set this value at the beginning of the function body.

 // original JavaScript code function foo(argument = true) { // some stuff } // representation of the Bytecode Generator's output in JavaScript function foo(argument) { argument = true; // some stuff } 


In the case of using the async keyword , if the default value causes an error (exception), the specification requires that the promise be refused. This makes it easy to catch exceptions.

To implement this in Chakra, the team had a choice of two options: change the AST, or implement this behavior directly in the bytecode generator. We chose the latter and moved the initialization of the arguments to the beginning of the function body directly in the byte code, since this is a simpler and more understandable solution within our engine. Since to catch errors from the default value, it was necessary to add a try / catch block , it was easier for us to directly change the byte code when detecting the async keyword.

Finally, the generated bytecode will resemble the result generated for such JavaScript code:

 // representation of the Bytecode Generator's output in JavaScript function foo(argument) { try { argument = true; } catch (error) { return Promise.reject(error); } return spawn(function* () { // keep this call as we are in an async function // some stuff }, this); } 


How to enable support for Microsoft Edge asynchronous features?


To enable experimental support for asynchronous functions in Microsoft Edge, go to the about: flags page in Microsoft Edge and select the “Enable experimental JavaScript features” option, as shown below:



Asynchronous functions are available in preview mode as part of the Windows Insider program , starting with Microsoft Edge 13.10547 . We will be glad to hear your feedback on the use of this functionality in your code on our Twitter @MSEdgeDev or via Connect .

- Etienne Baudoux , Software Development Engineer Intern, Chakra Team
- Brian Terlson , Senior Program Manager, Chakra Team

Source: https://habr.com/ru/post/269871/


All Articles