了解javascript的同窗想必对同步和异步的概念应该都很熟悉了,若是还有不熟悉的同窗,我这里举个形象的例子,好比咱们早上起床后要干三件事:烧水、洗脸、吃早饭,同步至关于咱们先烧水,水烧开了再洗脸,洗完脸再吃早饭,三件事顺序执行,一件干完了再干下一件;而异步至关于咱们在烧水的同时吃早饭(不洗脸就吃早饭不太卫生),吃完早饭再洗脸。显然异步比同步更加高效,省去了不少等待的时间,同步过程的执行时间取决于全部行为的总和,而异步过程的执行时间只取决于最长的那个行为,以下图所示:javascript
因为Javascript是单线程的,同时只能处理一件事,在上面的例子中这个单线程就是“我”,好比我不能同时洗脸和吃早饭同样。因此为了让执行效率提升,咱们要尽可能让这个线程一直处于忙碌状态而不是闲置状态,就像咱们不用干等烧水,能够同时去作其余事情,而烧水由系统的其余线程去处理(该线程不属于Javascript)。在计算机的世界中,不少I/O密集型的操做是须要等待的,好比网络请求、文件读写等,因此异步方法在处理这些操做会更加驾轻就熟。java
了解异步的意义以后,咱们来对比目前主流几种异步过程控制方法,探讨一下异步编程的最佳实践。git
首先callback和异步没有必然联系,callback本质就是类型为function的函数参数,对于该callback是同步仍是异步执行则取决于函数自己。虽然callback经常使用于异步方法的回调,但其实有很多同步方法也能够传入callback,好比最多见的数组的forEach
方法:es6
var arr = [1, 2, 3]; arr.forEach(function (val) { console.log(val); }); console.log('finish'); // 打印结果:1,2,3,finish
相似的还有数组的map
, filter
, reduce
等不少方法。github
常见的异步callback如setTimeout
中的回调:编程
setTimeout(function () { console.log("time's up"); }, 1000); console.log('finish'); // 打印结果:finish, time's up
若是咱们将延迟时间改成0
,打印结果仍将是finish, time's up
,由于异步callback会等函数中的同步方法都执行完成后再执行。数组
在实际项目中咱们常常会遇到这样的问题:下一步操做依赖于上一步操做的结果,上一步操做又依赖于上上步操做,而每一步操做都是异步的。。这样递进的层级多了会造成不少层callback嵌套,致使代码可读性和可维护性变的不好,造成所谓的Callback Hell,相似这样:promise
step1(param, function (result1) { step2(result1, function (result2) { step3(result2, function (result3) { step4(result3, function (result4) { done(result4); }) }) }) })
固然在不放弃使用callback的前提下,上面的代码仍是有优化空间的,咱们能够将它从新组织一下:浏览器
step1(param, callbac1); function callback1(result1){ step2(result1, callback2); } function callback2(result2){ step3(result2, callback3); } function callback3(result3){ step4(result3, callback4); } function callback4(result4){ done(result4); }
至关于将Callback Hell的横向深度转化为代码的纵向高度,变得更接近于咱们习惯的由上到下的同步调用, 复杂度没有变,只是看起来更清晰了,缺点就是要定义额外的函数、变量。将这一思想进一步延伸就有了下面的Promise。babel
Promise中文译为“承诺”,在Javascript中是一个抽象的概念,表明当前没有实现,但将来的某个时间点会(也可能不会)实现的一件事。举个实例化的例子:早上烧水,我给你一个承诺(Promise),十分钟后水能烧开,若是一切正常,10分钟以后水确实能烧开,表明这个promise兑现了(fullfilled),可是若是中途停电了,10分钟水没烧开,那这个promise兑现失败(rejected)。用代码能够表示为:
const boilWaterInTenMins = new Promise(function (resolve, reject) { boiler.work(function (timeSpent) { if (timeSpent <= 10) { resolve(); } else { reject(); } }); });
若是想提升浏览器对Promise的兼容性可使用babel或者第三方的实现(参考 github awesome promise)
咱们再来看Promise对于异步过程控制有怎样的提高,还基于上面Callback Hell的例子,若是用Promise实现会如何呢?
首先咱们须要将step1
~ done
的函数用Promise实现(即返回一个Promise),而后进行一连串的链式调用就能够了:
stepOne(param) .then((result1) => { return step2(result1) }) .then((result2) => { return step3(result2) }) .then((result3) => { return step4(result3) }) .then((result4) => { return done(result4) }) .catch(err => handleError(err));
是否是简单不少!
若是你不太习惯Promise的调用方式,那咱们能够用async/await将其转化成更接近同步调用的方式:
async function main() { try { var result1 = await step1(param); var result2 = await step2(result1); var result3 = await step3(result2); var result4 = await step4(result3); done(result4); } catch (err) { handleError(err); } } main();
Generator是一个更加抽象的概念,要弄懂什么是Generator首先要理解另外几个概念Iterable Protocol(可迭代协议),Iterator Protocol(迭代器协议)和 Iterator(迭代器)。
Iterable Protocol 的特色能够归纳为:
Symbol.iterator
的方法for...of
遍历Javascript Array就实现了Iterable Protocol,除了常规的取值方式,咱们也能够利用array的Symbol.iterator
:
var arr = [1, 2, 3]; var iterator = arr[Symbol.iterator](); iterator.next(); // {value: 1, done: false}
咱们也能够修改Array默认的迭代方式,好比返回两倍的值:
Array.prototype[Symbol.iterator] = function () { var nextIndex = 0; var self = this; return { next: function () { return nextIndex < self.length ? { value: self[nextIndex++] * 2, done: false } : { done: true } } }; } for(let el of [1, 2, 3]){ console.log(el); } // 输出:2,4,6
Iterator Protocol 的特色能够归纳为:
{value: any, done: boolean}
value
为返回值,done
为true
时value
能够省略done
为true
表示迭代结束,此时value
表示最终返回值done
为false
,则能够继续迭代,产生下一个值显然Iterator就是实现了Iterator Protocol的对象。
理解上面几个概念后,理解Generator就简单多了,generator的特色可归纳为:
最简单的generator function好比:
function* gen() { var x = yield 5 + 6; } var myGen = gen(); // myGen 就是一个generator
咱们能够调用next方法来得到yield
表达式的值:
myGen.next(); // { value: 11, done: false }
但此时x
并无被赋值,能够想象成javascript执行完 yield 5 + 6
就停住了,为了继续执行赋值操做咱们须要再次调用next
,并将获得的值回传:
function* gen() { var x = yield 5 + 6; console.log(x); // 11 } var myGen = gen(); console.log(myGen.next()); // { value: 11, done: false } console.log(myGen.next(11)); // { value: undefined, done: true }
说了这么多,generator和异步到底有什么关系呢?咱们来看Promise + Generator 实现的异步控制(step1 ~ done 返回Promise):
genWrap(function* () { var result1 = yield step1(param); var result2 = yield step2(result1); var result3 = yield step3(result2); var result4 = yield step4(result3); var result5 = yield done(result4); }); function genWrap(genFunc) { var generator = genFunc(); function handle(yielded) { if (!yielded.done) { yielded.value.then(function (result) { return handle(generator.next(result)); }); } } return handle(generator.next()); }
和async/await相似,这种实现也将异步方法转化成了同步的写法,实际上这就是 ES7中async/await的实现原理(将genWrap替换为async,将yield替换成await)。
但愿本文对你们有点帮助,能更深入的理解javascript异步编程,能写出更优雅更高效的代码。有错误欢迎指正。新年快乐!