"The Evolution of Asynchronous JavaScript" foreign language sorted out the development history of JavaScript asynchronous functions. First, asynchronous functions were realized through callback functions, and then they went through Promise/A and generator functions. In the future, they will be async functions. Thanks to Jingzhuang for translating this article, the content is as follows:
Now let us review the development of JavaScript asynchronous functions over the years.
Callback function Callbacks
It seems that everything should start from the callback function.
Asynchronous JavaScript
As we know, in JavaScript, asynchronous programming can only be done through first-class citizen functions in the JavaScript language: this way means that we can pass one function as a parameter of another function, in this The function passed in can be called inside the function (that is, the callback function). This is why the callback function was born: if you pass a function as a parameter to another function (at this time it is called a higher-order function), then inside the function, you can call this function to complete the corresponding task. The callback function has no return value (don't be tempted to use return) and is only used to perform certain actions within the function. Take a look at an example:
Something.save(function(err) { if (err) { //error handling return; // 没有返回值 } console.log('success'); });
In the above example, we demonstrated an error-first callback function (error-first callbacks), which is also one of the characteristics of Node.js itself. All core modules in Node.js and most modules in the NPM repository This feature will be followed when writing.
Challenges in overusing callback functions:
If you cannot organize the code reasonably, it is very easy to cause callback hell (callback hell), which will make your code difficult to be understood by others.
It's easy to miss error handling code.
The return statement cannot be used to return a value, and the throw keyword cannot be used.
It is for these reasons that in the JavaScript world, we are always looking for feasible solutions that can make asynchronous JavaScript development easier.
One of the possible solutions is the async module. If you have been dealing with callback functions for a long time, you may have a deep understanding that in JavaScript, if you want to make something execute in parallel, or serially, or even use an asynchronous function to map something in an array How complex is the element using async functions. So, thanks to Caolan McMahon for writing the async module to solve these problems.
Using the async module, you can easily write code in this way:
async.map([1, 2, 3], AsyncSquaringLibrary.square, function(err, result){ // result will be [1, 4, 9] });
Although the async module brings convenience to a certain extent, it is still not simple enough and the code is not easy to read, so Promise appeared.
Promise
The current JavaScript asynchronous standard dates back to 2012 and did not become available until ES6. However, the term Promise was not invented by the JavaScript community. The term comes from an article published by Daniel P.friedman in 1976.
A Promise represents the final result of an asynchronous operation.
Now we use Promise to complete the tasks completed by the above code. The Promise style code is as follows:
Something.save() .then(function() { console.log('success'); }) .catch(function() { //error handling })
You will find that callback functions are also used in Promise. A callback function is passed in both the then and catch methods, which are executed when the Promise is satisfied and rejected respectively. Another advantage of Promise functions is that they can be chained to complete a series of tasks. For example, you can write code like this:
saveSomething() .then(updateOtherthing) .then(deleteStuff) .then(logResults);
When you don’t have a ready-made Promise, you may need to resort to some Promise libraries. A popular choice is to use bluebird. These libraries may provide more functionality than native solutions and are not limited to the features specified by the Promise/A standard.
But why don’t you use sugar methods? It is recommended that you first read the article Promise: The Extension Problem. For more information about Promise, please refer to the Promise/A standard.
You may ask: If most libraries only expose callback interfaces, then how should I use Promise?
Well, this is very simple. The only thing you need to do at this time is to use Promise to wrap the function call body containing the callback. For example:
Callback style code might look like this:
function saveToTheDb(value) { db.values.insert(value, function (err, user) { if (err) throw err; // todo: insert user to db }); }
Now we change it to code that supports Promise style calling:
function saveToTheDb(value) { return new Promise(function(resolve, reject) { db.values.insert(value, function(err, user) { // remember error first ;) if (err) { return reject(err); // don't forget to return here } resolve(user); }) } }
A considerable number of libraries or frameworks already support both methods, that is, they provide both callback-style and Promise-style API interfaces. So now, if you also want to provide a library to the outside world, the best practice is to provide both interfaces at the same time. You can easily use the following methods to achieve this purpose:
function foo(cb) { if (cb) { return cb(); } return new Promise(function (resolve, reject) { }); }
或者更简单些,你可以从只提供Promise风格的接口开始后,并使用诸如 callbackify这样的工具来达到向后兼容的目的。其实Callbackify所做的工作和上面的代码片段类似,但在实现上使用了一个更通用的方法, 我建议你可以去阅读Callbackify的源代码。
生成器Generators/ yield
JavaScript 生成器是个相对较新的概念, 它是ES6(也被称为ES2015)的新特性。想象下面这样的一个场景:
当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。
上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。
function* foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操作 } } var bar = foo(); // 返回的其实是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
更进一步的,如果你想更轻松的使用生成器函数来编写异步JavaScript代码,我们可以使用 co 这个库,co是著名的tj大神写的。
Co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。
使用co,前面的示例代码,我们可以使用下面的代码来改写:
co(function* (){ yield Something.save(); }).then(function() { // success }) .catch(function(err) { //error handling });
你可能会问:如何实现并行操作呢?答案可能比你想象的简单,如下(其实它就是Promise.all而已):
yield [Something.save(), Otherthing.save()]; Async/ await
在ES7(还未正式标准化)中引入了Async函数的概念,目前如果你想要使用的话,只能借助于babel 这样的语法转换器将其转为ES5代码。(提醒一点:我们现在讨论的是async关键字,而不是NPM中的async包)。
简而言之,使用async关键字,你可以轻松地达成之前使用生成器和co函数所做到的工作。当然,除了hack之外。
也许你会问,是否在ES7中有了async关键字,yield就变得不是那么重要了?
实际上,使用yield实现异步也不过是一种hack罢了,yield意味着懒次序(lazy sequences)和迭代器。 而await能够完美的分离这两点,首先让yield用于其最初的目的,其次使用await来执行异步操作。
在这背后,async函数实际使用的是Promise,也就是为什么async函数会返回一个Promise的原因。
因此,我们使用async函数来完成类似于前面代码所完成的工作,可以使用下面这样的方式来重新编写代码:
async function save(Something) { try { await Something.save(); // 等待await后面的代码执行完,类似于yield } catch (ex) { //error handling } console.log('success'); }
正如你看到的那样,使用async函数,你需要在函数声明的最前面加上async关键字。这之后,你可以在函数内部使用await关键字了,作用和之前的yield作用是类似的。
使用async函数完成并行任务与yiled的方式非常的相似,唯一不同的是,此时Promise.all不再是隐式的,你需要显示的调用它:
async function save(Something) { await Promise.all[Something.save(), Otherthing.save()] }
Koa也支持async函数,如果你也在使用koa,那么你现在就可以借助babel使用这一特性了。
import koa from koa; let app = koa(); app.experimental = true; app.use(async function (){ this.body = await Promise.resolve('Hello Reader!') }) app.listen(3000);
以上内容给大家分享了JavaScript异步函数发展历程,希望对大家有所帮助。