在进行源码阅读前
首先抱有一个疑问,thunk函数是什么,thunkify库又是干什么的,co又是干吗,它有啥用node
在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数做为参数git
试想下咱们在node环境下要使用fs.readfilegithub
fs.readfile('filename',function(err,data){ if(err){ console.log(err) return } })
而使用thunk简单改造以后咱们的函数能够变成这样子的形式数组
var Thunk = function(filename){ return function (callback){ return fs.readfile(fileName,callback) } }
此时调用readfile的话,咱们能够这么调用promise
var read = Thunk('filename') read(callback);
thunkify出自tj大神之手网络
var assert = require('assert'); module.exports = thunkify; function thunkify(fn) { assert('function' == typeof fn, 'function required'); // 引入断言库判断是否是函数 // 返回一个包含thunk函数的匿名函数 return function () { var args = new Array(arguments.length); // 建立一个数组空间 var ctx = this; // 获取上下文环境用于后面绑定上下文 for (var i = 0; i < args.length; ++i) { args[i] = arguments[i]; } // 迭代传参,由于有内存泄漏bug // 返回真正的thunk函数 return function (done) { // done至关因而执行后的callback var called; // 声明一个called保证只执行一次这个回调函数 // 压入一个数组中进行这种隔断,防止被屡次执行 args.push(function () { if (called) return; called = true; done.apply(null, arguments); }); // 用try catch 在执行失败也走一次callback 传入err信息 try { fn.apply(ctx, args); } catch (err) { done(err); } } } };
代码并不难懂
乍一看,这好像没什么用吧。并发
但 js后来有一个Generator函数,thunk此时仿佛有了做用app
使用yield 就是将控制权放出暂停执行
而后返回一个当前指针(遍历器对象)异步
因此咱们是否须要有一种方法接受而且能够继续返回这种控制权
显式的调用next当然没有问题。可是咱们要自动的话?该怎么办async
基于自动流程管理,咱们利用thunk函数的特性,调用回调函数callback
回调函数里面递归调用generator的next方法
直到状态值为done generator函数结束
这时候整个generator就能够很优雅地被解决
而后咱们想象,这个流程thunk函数能够干什么
主要的功能实际上是经过封装多层使得咱们能够在回调函数内得到控制权
返回控制权
由于通常按照正常写法
咱们须要显式地调用next next来使得咱们的Generator一步步完成
那么咱们只须要一种机制,能够帮助咱们得到控制权,而且返回控制权
均可以实现自动化
var fs = require('fs'); var thunkify = require('thunkify'); var readFile = thunkify(fs.readFile); var gen = function* (){ var r1 = yield readFile('xxxfilename'); console.log(r1.toString()); var r2 = yield readFile(' xxxfilename '); console.log(r2.toString()); }; function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if (result.done) return; result.value(next); } next(); } run(gen);
这是一个简单的demo利用thunkify实现自动化generator
thunk函数回调调用next是一种方法
Pormise的then调用next 同时也是一种解决办法
区别在于thunk可控(指的是在回调中咱们能够可控执行),promise当即执行
Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.
基于Generator,使用promise,让你用一种更好的方式书写异步代码
co的源码也并很少
大概两百行
https://github.com/tj/co/blob...
要读懂co源码建议还得看看promise规范与用法
var slice = Array.prototype.slice; module.exports = co['default'] = co.co = co; co.wrap = function (fn) { //兼容有参数的generator函数 //利用柯里化将generator转换成普通函数 createPromise.__generatorFunction__ = fn; return createPromise; function createPromise() { return co.call(this, fn.apply(this, arguments)); } }; function co(gen) { var ctx = this; //得到当前上下文环境 var args = slice.call(arguments, 1); //得到多参数(若是有的话) // we wrap everything in a promise to avoid promise chaining, // which leads to memory leak errors. // see https://github.com/tj/co/issues/180 //会内存泄漏 //返回一个promise至关于将一切都包裹在promise里面。使得咱们co返回的可使用promise的方法 // co的返回值是Promise对象。为何能够then和catch的根源 return new Promise(function(resolve, reject) { //作类型的判断。 if (typeof gen === 'function') gen = gen.apply(ctx, args); //Generator函数执行以后会是typeof会是对象。 //默认执行调用一次Generator返回一个遍历器对象Generator if (!gen || typeof gen.next !== 'function') return resolve(gen); //判断类型 若是不符合 promise就进入resolved // 看看是否是Generator指针 //传入的不是Generators函数,没有next, // 就直接resolve返回结果;这里是错误兼容而已,由于co就是基于generator function的,传入其余的没有意义 //执行onFulfilled onFulfilled(); //返回一个promise //onFulfilled干了什么。其实跟咱们以前的同样,只是这里涉及到了promise的状态。若是出错了。状态返回是reject function onFulfilled(res) { var ret; try { ret = gen.next(res); //初始化启动一遍Generator next } catch (e) { return reject(e); //一有错误的话就抛出错误转向rejected } // 初始化即将第一次yield的·值·传给next next(ret); //将这个指针对象转交next函数处理 // 实现自动化的关键 return null; } function onRejected(err) { //接受error错误 var ret; //这块其实就是处理整个流程的错误控制 try { ret = gen.throw(err); //利用Generator throw错误给try catch捕获 } catch (e) { return reject(e); //使得Promise进入rejected } next(ret); } function next(ret) { //接受指针对象 if (ret.done) return resolve(ret.value); //显示对ret指针状态作判断,done为true证实generator已经结束 //此时进入resolved结束整个Generator var value = toPromise.call(ctx, ret.value); //将yield 的值进行Promise转换 if (value && isPromise(value)) return value.then(onFulfilled, onRejected); //value在咱们容许的范围内,那么value.then注入onFulfilled与onRejected,来执行下一次gen.next。 //在onFulfilled又将调用next从而使得next不停的利用then作调用 //若是值是存在而且能够进行promise的转换。(也就是否是基本类型/或假值) return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); //若是没有通过值转换或者value为空的时候。此时将抛出错误。 //由于那就是所谓的基本类型不支持了 //function, promise, generator, array, or object只支持这几种的 } }); } //注意咱们就只容许这几种类型转换。 //那么进入判断的时候咱们就能够很简单地判断了,而后决定promise的状态 function toPromise(obj) { if (!obj) return obj; //若是obj undefined 或者别的假值返回这个undefined if (isPromise(obj)) return obj; //若是是个Promise的话就返回这个值 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); //判断是否是Generator function是的话用co处理 if ('function' == typeof obj) return thunkToPromise.call(this, obj); //若是是函数的话,使用thunk to promise转换 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); //若是是数组 使用array to promise if (isObject(obj)) return objectToPromise.call(this, obj); //若是是对象 使用object to promise 转换 return obj; //若是都不是 就返回`值` } // co关于yield后边的值也是有必定的要求的,只能是一个 Function|Promise|Generator|Generator Function | Array | Object; // 而 yield Array和Object中的item也必须是 Function|Promise|Generator | Array | Object; // 若是不符合的话就将Promise rejected掉并发出警告 //下面是一些工具函数 //使用thunk后的fnction 咱们只容许它有一个参数callbak //容许有多个参数 第一个参数为error //在node环境下 第一个为error对象 function thunkToPromise(fn) { var ctx = this; return new Promise(function (resolve, reject) { fn.call(ctx, function (err, res) { if (err) return reject(err); if (arguments.length > 2) res = slice.call(arguments, 1); resolve(res); }); }); } // thunkToPromise传入一个thunk函数 // 函数返回一个Promise对象 // promise里面执行这个函数 // nodejs的回调函数 第一个参数都是err // 若是有错误就进入rejected(前面咱们能够看到 value.then(onFulfilled, onRejected); ) // 若是有error就rejected了 // 若是没有的话就调用resolve( 后面onFulfilled ) //将数组中的全部值均promise化后执行,Promise.all会等待数组内全部promise均fulfilled、或者有一个rejected,才会执行其后的then。 //对一些基本类型 例如数字 字符串之类的,是不会被toPromise转换的 //最后在resolve(res)的时候 res就是存有全部异步操做执行完的值数组 function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); } //对象经过key进行遍历, //对于每一个被promise化好的value //都将其存储于promises中,最后Promise.all, //生成results。 //objectToPromise实现实在是太可怕了=-= //因此不少字企图把它讲顺了 function objectToPromise(obj){ var results = new obj.constructor(); var keys = Object.keys(obj); var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); // 确保obj[key]为promise对象 // 而后调用defer推入 promises等待value的promise resolved以后将key放入results // 不然直接将 results[key] = obj[key](也就是无须promise化的) if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } // 利用promise.all来使用异步并行调用咱们的promises // 若是执行后进入resolved而后压入results对象 // 最后固然是返回这个results对象 // 而后后面的then在得到时候 onFulfilled onRejected的参数将是这个results // 这样子咱们每一个promise的结果都会存在result对象对应的key内 // 返回的是一个promise 后面也就能够接着.then(onFulfilled) return Promise.all(promises).then(function () { return results; }); function defer(promise, key) { // predefine the key in the result results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; })); } } //检查是否是promise //·鸭子类型·判断。 function isPromise(obj) { return 'function' == typeof obj.then; } //判断是否是Generator迭代器 function isGenerator(obj) { return 'function' == typeof obj.next && 'function' == typeof obj.throw; } //判断是否是generator函数 function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; return isGenerator(constructor.prototype); } //判断是否是对象 //plain object是指用JSON形式定义的普通对象或者new Object()建立的简单对象 function isObject(val) { return Object == val.constructor; }
co大概就是干的,将generator自动化,更好的将异步流转换同步写法
ES7的async await
其实就是generator的语法糖 再加上一个内置自动执行的混合体
也就是究极体
await的返回值是一个promise
参考内容:
阮一峰网络日志
co 源码分析 co 与 co.wrap
co 源码分析
但愿阅读此文的人能够有一些收获。若是有什么错误的地方也但愿能够谈出来或者私信我,一块儿探讨。渴望成长。^v^