以前我在关于Promise的文章中提到了co
这个库。在这篇文章里,我将写一写本身对它的认识。git
Trust me,用了co
库,你不想用别的,来它半斤异步调用你一口能吃仨。github
可是我对Tj大神的co库源码谈不上深刻理解。因此,若有乱讲,欢迎指正。segmentfault
我这里默认读者对Promise
和Generator
有必定的认识。数组
先安利本身写的两篇关于Promise的文章:浏览器
下面我就来谈谈co
这个牛逼的库。async
干吗,咱们不是讲ES6么,怎么跳到ES7了?函数
由于co
要作的事情,就是ES7的async/await
要作的事情。
也就是说,这种解决异步的思路,已经在ECMA标准的考虑之中了。未来咱们浏览器的JS引擎就能够原生实现这件事而不是经过JavaScript代码模拟。要知道,引擎的实现和代码的实现那是彻底两码事。ui
多一句嘴:有些同窗混淆了ECMA标准、引擎支持和代码实现的联系。
这里引用老赵在知乎里面回答问题时说的一句话:
ES7是个标准,定义的是what to do不是how to do,为何好多人仍是搞不清这二者的区别。
ECMAScript定义了一些JavaScript语言层面要作的事情,这是一个标准。之因此要制定这个标准,是为了防止浏览器各自为政而出现JS引擎对同一行代码的解释出现不一样的状况。
也就是说,ECMA制定标准,咱们就能够按照这个标准来写JavaScript代码。写好的JavaScript代码由浏览器的JS引擎来解释,最终变成计算机能读懂的代码来执行。
上代码:
var foo = function(){ return new Promise(resolve => { // 异步操做以后 resolve('OK'); }); } async funtion bar(){ var result = await foo(); console.log(result); } bar(); // ==> 打印'OK'
咱们注意到,这段代码用了两个新的关键字async
和await
。并且有两件神奇的事情发生了:
bar
函数中包含了一个返回Promise
对象的语句,并且Promise
中存在异步代码。可是这条语句接下来的语句明显是等待Promise
对象中的代码异步执行完毕以后才执行的。(不然不会获得异步以后的值)
Promise
对象resolve
的值,并无在then
中进行处理,而是直接做为返回值返回到Promise
对象外面了.
这就是async/await
的魔法。在函数前面加上async
关键字以后,内部的代码会识别await
关键字。此时假设await
后面的语句返回一个Promise
对象,那么执行的代码将会等待,直到Promise
对象变为resolve
状态。而且Promise
对象中resolve
的值将直接做为await
语句的返回值返回。而后再执行await
语句以后的语句。
今后咱们就能够无痛的撸异步代码,妈妈不再用担忧回调金字塔的出现和异步流程逻辑搞不定的状况了!
另外一个奇妙的事情就是,率先支持这一特性的浏览器竟然是微软的Edge。大概是由于C#
语言早就出现async/await
,而且TypeScript
也支持这一特性的缘故吧。
咱们但愿全部的浏览器都及早支持这一特性。可是值得欣喜的一点就是,虽然V8尚未支持,Tj大神早就利用Generator
的方式实现了一个ES6版本的async/await
!(膜拜脸)
一样是上面的逻辑,咱们用co
实现一次:
// 首先咱们须要将co引入,假设咱们使用commonJS的方式 const co = require('co'); var foo = function(){ return new Promise(resolve => { // 异步操做以后 resolve('OK'); }); } co(function* (){ var result = yield foo(); console.log(result); }); // ==> 打印'OK'
咱们看到,co
函数接收一个Generator
生成器函数做为参数。执行co
函数的时候,生成器函数内部的逻辑像async
函数调用时同样被执行。不一样之处只是这里的await
变成了yield
。
要实现以上的逻辑,结合Generator
的特性,co
函数应该:
在函数体内将Generator生成器函数执行并生成生成器实例(在此命名为gen
),而后经过gen.next
方法的调用,不断执行生成器函数内部的代码。
执行next
方法以后,返回的Promise
在生成器函数执行环境以外执行,并取出resolve
值,做为返回值做为next
方法的参数返回到Generator
执行环境中。
基于以上两点,咱们能够大致实现一个简化版的co
,代码以下:
const co = function(genFunc){ const gen = genFunc(); // 获得生成器实例 const deal = (val) => { const res = gen.next(val); // 这里处理了异步逻辑, // 在回调中去递归,不断执行next // 这样就将resolve的值传回了Generator res.value.then(result => deal(result)); } deal(); // 第一次触发递归 }
去掉括号等等,只有短短六行代码。
原理性的东西大约就是这样了。可是co
作的不止这些。
以前co
的yield
后的语句并不支持Promise
对象,而是一个特殊的函数,叫作thunk
。目前co
两者都支持。
此处我并不打算重复性解释thunk
版本,由于原理性的东西实现起来是差很少的。
co
函数是有返回值的,也是一个Promise
对象。
当生成器函数内的逻辑执行完毕且没有错误以后,这个Promise
对象(co
返回值)变为resolve
状态,且将生成器的返回值做为resolve
出来的值。
若生成器函数内返回一个Promise
对象,那么co
函数返回值就是这个Promise
对象。
若生成器函数抛出了错误,那么这个错误做为reject
出来的值,将Promise
对象的状态变为reject
。
这样咱们就能够将错误放进其返回值的.catch
方法中统一处理。
在生成器函数内部,咱们也可使用try...catch
语句获取错误对象。
生成器的yield
后面能够跟一个元素值为Promise
对象的数组,这个数组内Promise
对象内的异步逻辑将并发执行,并返回一个数组。(相似于Promise.all
方法)
假设生成器执行以前须要从外部传入参数,co
库提供了一个方法:
var fn = co.wrap(function* (val) { return yield Promise.resolve(val); }); fn(true).then(function (val) { });
以上是一点微小的看法。谢谢指正。