一道面试题引发的血战。
问:怎么理解异步的发展过程,例如axios、ajax、promise、await、async、generator等?
-
为什么需要异步?
JavaScript是单线程语言,同一时间只能做一件事情;
同步任务进入主线程,按顺序执行,意味着阻塞,前一段代码必须执行完成了才能执行后边代码;
异步是不会造成阻塞的。异步任务进入任务队列,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程。比如ajax、setTimeout/setInterval等。上述过程不断循环,称为事件循环(EventLoop);
-
事件循环是js实现异步的一种方式,也是js的执行机制 。
题外话:
1. 除了同步、异步任务,对任务有更精细的定义:宏任务[整体代码的script/setTimeout/setInterval]、微任务[Promise/process.nextTick];
2. js的执行和运行有很大区别,js在不同环境下,比如node、浏览器等,执行方式(即执行机制,事件循环eventLoop)是不同的。而运行是指js解析引擎大多是统一的。
3. 关于事件循环机制,详细请参考
-
发展过程
-
异步最早的解决方案是回调函数,比如:
1.setTimeout/setInterval
2 ajax
3 nodeAPI
4 事件回调
例如这样一个需求:先读取文件A,再根据文件A的内容去读取文件B,然后根据文件B的内容读取文件C...let fs = require('fs'); fs.readFile('1.txt', 'utf-8', function(err, data) { if (err) { console.log(err); } else { console.log(data); } }); // 回调地狱 fs.readFile(A, function (err, data) { fs.readFile(B, function (err, data) { fs.readFile(C, function (err, data) { // ... }); }); });
优点是简单;缺点是回调嵌套难以维护,不方便统一处理错误,不能try catch错误以及回调地狱。
-
Promise一定程度上解决了“回调地狱”和统一处理错误问题。
好像Promise.all()方法也能实现,我们来看一看:Promise.all([ readfilePromise('A'), readfilePromise('B'), readfilePromise('C'), ]) .then((dataA, dataB, dataC) => { // ... }) .catch(err => console.log(err));
实际上这个方法的含义是等A、B、C三个文件全部读取完成后,进行操作,三个文件是独立的,不是上述栗子中的依赖关系。详细请参考MDN
// Promise解决“回调地狱”以及统一处理错误问题 let readfilePromise = function (filepath) { return new Promise(function (resolve, reject) { // console.log(a); fs.readFile(filepath, function(err, data) { if (err) { reject(err); } else { resolve(data); } }); }); } readfilePromise('A') .then(data => { return readfilePromise('B'); }) .then(data => { return readfilePromise('C'); }) .catch(err => console.log(err)); // 该catch可以统一处理其前边所有错误,包括Promise内部的语法错误、异步reject、then里的错误等,且不会影响到Promise外部的代码
好像解决了一些问题,但一定程度上又存在新问题:
1.错误不能被try catch,只有通过catch回调捕获;
2.使用promise的链式回调没从根本上解决回调地狱问题,它是一种新的写法,而不是新的语法,比如后一个请求依赖于前一个的结果,仍然需要嵌套多个promise实例或者then(...); Generator函数是es6提供的一种新的异步编程解决方案,遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续向后执行。缺点是需要结合co函数,否则不能自动执行;
-
async/await是generator的语法糖,内置执行器,使得异步代码看起来像同步代码,可以try catch错误;
async function readdependantFile () { try { let dataA = await readfilePromise('A'); let dataB = await readfilePromise('B'); let dataC = await readfilePromise('C'); } catch (err) { console.log(err); } } readdependantFile();
-
-
总结:异步编程的目标是让异步逻辑的代码看起来像同步一样。
回调函数 --> Promise --> Generator --> async/await