博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Koa源码分析(二) -- co的实现
阅读量:6303 次
发布时间:2019-06-22

本文共 5473 字,大约阅读时间需要 18 分钟。

Abstract

本系列是关于Koa框架的文章,目前关注版本是Koa v1。主要分为以下几个方面:

co

大名鼎鼎的co是什么?它是基于ES6的一些新特性开发的异步流程控制库,基于它所开发的被视为未来主流的web框架。

koa基于co实现,而co又是使用了ES6的generator和promise特性。如果还不理解,可以查看阮一峰老师的和。目前co升级为4.X版本事,代码进行了一次颇有规模的重构,我们主要关注co(4.X)的实现思路和源码分析。

使用示例

co(function* (){    var a = yield Promise.resolve('one');    console.log(a);    var b = yield Promise.reslove('two');    console.log(b);    return 'three';}).then((value) => console.log(value));// one// two// three
co(function* (){    var res = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];    return res;}).then((value) => console.log(res));// [1, 2, 3]

根据co的功能,它作为异步流程控制的作用,自动调用generator对象的next()方法,实现generator函数的运行,并返回最终运行的结果。

如果要涉及到co的实现细节,我们就会存在以下几个疑问:

  1. 如何依次调用next()方法

  2. 如何将yield后边运算异步结果返回给对应的变量

  3. co自身如何返回generator函数最后的return值

接下来我们正对以上问题,分析TJ大神的源码

源码解析

co源码的流程控制

function co(gen) {    // 保持当前函数的上下文    var ctx = this;    // 截取co输入的参数,剔除arguments中的第一个参数,即gen对象,剩余参数作为gen的入参    var args = slice.call(arguments, 1);        // 返回一个Promise对象,即最外围Promise对象    return new Promise(function(resolve, reject) {        // 判断传入的gen是否为函数,若是则执行,将结果赋值给gen对象        // 若不是,则不执行        if (typeof gen === 'function') gen = gen.apply(ctx, args);        // 根据generator函数执行结果是否存在next字段,判断gen是否为generator迭代器对象        // 若不是,则调用resolve返回最外围Promise对象的状态        if (!gen || typeof gen.next !== 'function') return resolve(gen);                // 若是generator迭代器对象,开始控制gen.next()方法的调用        onFulfilled();        // 两个用途        // 一、generator函数的执行入口        // 二、当做所有内部Promise对象的resolve方法,处理异步结果,并继续调用下一个Promise        function onFulfilled(res) {            var ret;            try {                // gen运行至yield处被挂起,开始处理异步操作,并将异步操作的结果返回给ret.value                ret = gen.next(res);            } catch (e) {                // 若报错,直接调用reject返回外围Promise对象的状态,并传出错误对象                return reject(e);            }            // 将gen.next的执行结果传入next函数,实现依次串行调用gen.next方法            next(ret);            return null;        }        // 当做所有内部Promise对象的reject方法,处理异步结果,并继续调用下一个Promise        function onRejected(err) {            var ret;            try {                ret = gen.throw(err);            } catch (e) {                // 若报错,直接调用reject返回外围Promise对象的状态,并传出错误对象                return reject(e);            }            // 将gen.throw的执行结果传入next函数,实现依次串行调用gen.next方法            next(ret);        }                // 实现串行调用gen.next的核心        function next(ret) {            // 判断内部Promise是否全部执行完毕            // 若执行完毕,直接调用resolve改变外围Promise的状态,并返回最终的return值[问题3]            if (ret.done) return resolve(ret.value);            // 若未执行完毕,调用toPromise方法将上一个Promise返回的值转化为Promise对象            // 具体参见toPromise方法            var value = toPromise.call(ctx, ret.value);            // 根据value转化后的Promise对象的两个状态,执行下一个next方法            if (value && isPromise(value)) return value.then(onFulfilled, onRejected);            // 抛出不符合转化规则的类型的值            return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '                + 'but the following object was passed: "' + String(ret.value) + '"'));            }        });    }

源码分析完了,我们可以把co串行调用generator函数中yield的过程总结如下:

  1. 进入外围Promise

  2. 通过入口onFilfilled()方法,将generator函数运行至第一个yield处,执行该yield后边的异步操作,并将结果传入next方法

  3. 如果next中传入结果的done为true,则返回外围Promise的resolve

  4. 如果next中传入结果的done为true,则返回value(即yield后边的对象)是否可以转化为内部Promise对象。如无法转化,则抛出错误,返回外围Promise的reject

  5. 若能转化为Promise对象,将所有内部Promise并行执行,通过then(onFilfilled, onRejected)开始执行

  6. 在onFilfilled()或者onRejected()内部调用再次调用next()方法,实现串行执行yield,并肩yield后边的对象传递给next(),依次重复。

  7. 所有yield执行返回,将最后的return值返回给外围Promise的resovle方法,结束co对generator函数的调用

yield后面对象转化为Promise

能够在co中实现generator函数的逐步调用next()方法,转化为内部Promise将至关重要,而源码是如何转化的呢?哪些对象又是能够转化的呢?接下来,我们看下源码。

function toPromise(obj) {    // 确保obj有意义    if (!obj) return obj;    // 若是Promise对象,则直接返回    if (isPromise(obj)) return obj;    // 若是generator函数或者generator对象,则传入一个新的co,并返回新co的外围Promise    // 作为当前co的内部Promise,这样实现多层级调用    if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);    // 若是函数,则返回thunk规范的函数    if ('function' == typeof obj) return thunkToPromise.call(this, obj);    // 若是数组,把数组中每个元素转化为内部Promise,返回Promise.all并行运算    if (Array.isArray(obj)) return arrayToPromise.call(this, obj);    // 若是对象,遍历对象中的每个key对应的value,转化成Promise.all并行运算    if (isObject(obj)) return objectToPromise.call(this, obj);    return obj;}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);    });  });}function arrayToPromise(obj) {    // Array.map并行计算返回每一个元素的Promise    return Promise.all(obj.map(toPromise, this));}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]);    if (promise && isPromise(promise)) defer(promise, key);    else results[key] = obj[key];  }  // Promise链式调用,后续的then能偶获取此处的results  return Promise.all(promises).then(function () {    return results;  });  function defer(promise, key) {    // key对应的元素成功转化为Promise对象后,构造这些Promise的resovle方法    // 以便在results中获取每个Promise对象成功执行后结果    results[key] = undefined;    promises.push(promise.then(function (res) {      results[key] = res;    }));  }}

结合上述分析,我们可以得到,yield后面只能是函数、Promise对象、Generator函数、Generator迭代器对象、数组(元素仅限之前的4类)和Object(对应value仅限定之前的4类)

转载地址:http://agfxa.baihongyu.com/

你可能感兴趣的文章
请问view controller scene,该如何删除
查看>>
bootstrap新闻模块样式模板
查看>>
zzzzw_在线考试系统①准备篇
查看>>
App Store 审核被拒的23个理由
查看>>
剑指offer第二版-1.赋值运算符函数
查看>>
javascript 对象
查看>>
Android学习笔记——文件路径(/mnt/sdcard/...)、Uri(content://media/external/...)学习
查看>>
Echart:前端很好的数据图表展现工具+demo
查看>>
CATransform3D iOS动画特效详解
查看>>
Linux VNC黑屏(转)
查看>>
Java反射简介
查看>>
react脚手架应用以及iview安装
查看>>
shell学习之用户管理和文件属性
查看>>
day8--socket网络编程进阶
查看>>
node mysql模块写入中文字符时的乱码问题
查看>>
仍需"敬请期待"的微信沃卡
查看>>
分析Ajax爬取今日头条街拍美图
查看>>
内存分布简视图
查看>>
POJ 2918 求解数独
查看>>
如何学习虚拟现实技术vr? vr初级入门教程开始
查看>>