co源码分析(thunk版本3.1.0)
Posted mengff
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了co源码分析(thunk版本3.1.0)相关的知识,希望对你有一定的参考价值。
co的thunk版本,就是将所有 函数,generator,generator function,object,array,promise,都转换为thunk函数,在thunk函数的回调中,切换外部包装的generator的状态,即调用next方法,来依次执行所有的异步任务。其中的,object和array,通过循环的方式,来并行执行thunk函数。
具体的源码注释如下:
//方便截取函数参数 var slice = Array.prototype.slice; //导出co函数 module.exports = co; //Wrap the given generator `fn` and return a thunk. //包裹generator,并且返回一个thunk函数,执行这个thunk函数,就可以开始整个异步流程 //var thunk = co(function* (){...}); //开始执行 thunk(); function co(fn) { var isGenFun = isGeneratorFunction(fn); return function (done) { var ctx = this; // in toThunk() below we invoke co() // with a generator, so optimize for // this case var gen = fn; // we only need to parse the arguments // if gen is a generator function. if (isGenFun) { var args = slice.call(arguments), len = args.length; //判断最后一个参数是否函数 var hasCallback = len && ‘function‘ == typeof args[len - 1]; //末参是回调,done=回调,不是记录错误 done = hasCallback ? args.pop() : error; //是一个generator function,就是执行它,得到一个generator gen = fn.apply(this, args); } else { //不是generator,就为入参,或记录错误 done = done || error; } //启动执行,next会递归调用,直至所有异步函数执行完毕 next(); // #92 // wrap the callback in a setImmediate // so that any of its errors aren‘t caught by `co` function exit(err, res) { setImmediate(function(){ done.call(ctx, err, res); }); } function next(err, res) { var ret; // res相当于err剩余的所有参数 if (arguments.length > 2) res = slice.call(arguments, 1); //有错误,generator抛出错误,退出 if (err) { try { ret = gen.throw(err); } catch (e) { return exit(e); } } //无错误,gen执行next,执行一次异步函数, if (!err) { try { ret = gen.next(res); } catch (e) { return exit(e); } } // done为true,执行完所有异步函数,退出 if (ret.done) return exit(null, ret.value); // 把ret.value得到的函数转换为thunk函数 ret.value = toThunk(ret.value, ctx); // ret.value转换为thunk函数成功 if (‘function‘ == typeof ret.value) { var called = false; try { ret.value.call(ctx, function(){ //防止重复调用 if (called) return; called = true; //递归调用next函数,继续执行下面异步函数,自执行核心部分 //此function是ret.value这个thunk函数的回调函数 //arguments是回调函数参数,传递给next,包含了err和res,作为gen的throw和next的参数 next.apply(ctx, arguments); }); } catch (e) { setImmediate(function(){ if (called) return; called = true; next(e); }); } return; } // ret.value转换为thunk函数失败,提示只能是以下类型 next(new TypeError(‘You may only yield a function, promise, generator, array, or object, ‘ + ‘but the following was passed: "‘ + String(ret.value) + ‘"‘)); } } } //Convert `obj` into a normalized thunk. //将任何类型转换为thunk function toThunk(obj, ctx) { //是generator function,obj.call(ctx)返回generator,再用co调用 if (isGeneratorFunction(obj)) { return co(obj.call(ctx)); } //是generator,用co直接调用 if (isGenerator(obj)) { return co(obj); } //是promise,转换为thunk函数 if (isPromise(obj)) { return promiseToThunk(obj); } //是函数,直接返回本身 if (‘function‘ == typeof obj) { return obj; } //是对象或者数组,转换为thunk函数 if (isObject(obj) || Array.isArray(obj)) { return objectToThunk.call(ctx, obj); } //都不是,直接返回对象,未成功转换为thunk function,co的next函数会报错,提示格式不符合 return obj; } // Convert an object of yieldables to a thunk. // 将thunk数组,或thunk对象,转换为一个thunk函数 // 即将 arr = [thunk1,thunk2] 转换为 一个thunk // 或将 obj = { key1: thunk1, key2: thunk2 } 转换为 一个thunk function objectToThunk(obj){ var ctx = this; var isArray = Array.isArray(obj); return function(done){ //获取keys,对象是key,数组是索引 var keys = Object.keys(obj); var pending = keys.length; //初始化results的为一个同长度数组,或同类型对象 var results = isArray ? new Array(pending) // predefine the array length : new obj.constructor(); var finished; //对象或数组长度为0,结束 if (!pending) { setImmediate(function(){ done(null, results) }); return; } // prepopulate object keys to preserve key ordering // 对象类型,results按照obj的key,全部初始化为undefined if (!isArray) { for (var i = 0; i < pending; i++) { results[keys[i]] = undefined; } } //所有对象或数组key对应的函数传入run函数执行 for (var i = 0; i < keys.length; i++) { run(obj[keys[i]], keys[i]); } function run(fn, key) { if (finished) return; try { //函数转化为thunk函数 fn = toThunk(fn, ctx); //fn不是function,则转化为thunk函数失败 if (‘function‘ != typeof fn) { //记录函数本身 results[key] = fn; //pending数量减小,当pending为0时候,执行done,结束 return --pending || done(null, results); } //fn是thunk函数,执行fn fn.call(ctx, function(err, res){ if (finished) return; //报错,结束,传递错误 if (err) { finished = true; return done(err); } //在results中,用对应的key记录fn执行结果,合法函数最终被记录 results[key] = res; //减小待处理数量,为0,就结束 --pending || done(null, results); }); } catch (err) { //有任何报错,就结束 finished = true; done(err); } } } } //promsie转thunk function promiseToThunk(promise) { //返回只有一个回调参数fn的函数 return function(fn){ //回调fn遵循error first原则,在then的第一个回调中,error是null,data是res,传入fn(null,res) //在then的第二个回调中,fn直接用作error callback,只接收一个err参数 //最终fn是形如 fn(err,data) 这种形式 promise.then(function(res) { fn(null, res); }, fn); } } //判断是Promise function isPromise(obj) { return obj && ‘function‘ == typeof obj.then; } //generator function的返回值,就是一个interator function isGenerator(obj) { return obj && ‘function‘ == typeof obj.next && ‘function‘ == typeof obj.throw; } // 形如 function* (){...} 都是 generator function function isGeneratorFunction(obj) { return obj && obj.constructor && ‘GeneratorFunction‘ == obj.constructor.name; } //判断是对象 function isObject(val) { return val && Object == val.constructor; } /** * Throw `err` in a new stack. * * This is used when co() is invoked * without supplying a callback, which * should only be for demonstrational * purposes. * * @param {Error} err * @api private */ function error(err) { if (!err) return; setImmediate(function(){ throw err; }); }
以上是关于co源码分析(thunk版本3.1.0)的主要内容,如果未能解决你的问题,请参考以下文章
Redux异步解决方案之Redux-Thunk原理及源码解析
使用带有 promise 而不是 thunk 的 co 库有啥好处?
Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段
Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段