摘要:模块可以将异步解放成同步。源码分析使用的模块版本号为首先看一些用于判断对象类型的函数对数组方法的引用这两个应该就不用说了吧。。。看一下模块的输出部分因此以下三种用法等价接着就是重头戏函数了。
写在前面本文只在个人博客和 SegmentFault 社区个人专栏发表,转载请注明出处
个人博客: https://zengxiaotao.github.io
SegmentFault 个人专栏: https://segmentfault.com/blog...
学 nodejs 当然避免不了学习框架,毕竟原生的 API 较底层。最先接触的是 Koa 。看看官网的描述
next generation web framework for node.js
我翻译一下就是: 基于 node.js 的下一代 web 开发框架。好像很厉害的样子!koa 是一个轻量级的框架,本质上提供了一个架子,通过 各种中间件的级联的方式实现特定的功能。koa 借助 promise 和 generator , 很好解决了异步组合问题。
那什么又是 co 。学习 koa 就一定少不了学习 co 模块。co 模块可以将异步解放成同步。co 函数接受一个 generator 函数作为参数,在函数内部自动执行 yield 。
co 源码分析使用的 co 模块版本号为 4.6.0
首先看一些用于判断对象类型的函数
var slice = Array.prototype.slice; // 对数组 slice 方法的引用
function isObject(val) { return Object == val.constructor; }
这两个应该就不用说了吧。。。
function isPromise(obj) { return "function" == typeof obj.then; }
判断一个对象是否是一个 promise 实例,判断的依据也很简单,根据 “鸭子类型”,判断这个对象是否有 then 方法
function isGenerator(obj) { return "function" == typeof obj.next && "function" == typeof obj.throw; }
类似的,判断一个对象时候是 generator 实例,只需判断这个对象是否具有 next 方法和 throw 方法。
function isGeneratorFunction(obj) { var constructor = obj.constructor; if (!constructor) return false; if ("GeneratorFunction" === constructor.name || "GeneratorFunction" === constructor.displayName) return true; return isGenerator(constructor.prototype); }
判断是否是一个 generator 函数,只需判断这个函数是否是 GeneratorFunction 函数的实例
以上所讲的在之后将 value 包装成 promise 实例时都会用到。
看一下 co 模块的输出部分
module.exports = co["default"] = co.co = co
因此以下三种用法等价
var co = require("co") // (1) var co = require("co").co // (2) var co = require("co").default // (3)
接着就是重头戏 co 函数了。
function co(gen) { var ctx = this; // 保存函数的执行上下文对象 var args = slice.call(arguments, 1) // 传给 gen 函数的参数 // 返回一个 promise 实例 return new Promise(function(resolve, reject) { // 根据传入的 generator 函数生成一个 generator 实例 if (typeof gen === "function") gen = gen.apply(ctx, args); // 如果生成的 gen 不是一个 generator 实例, // promise 直接变成 resolved 状态 if (!gen || typeof gen.next !== "function") return resolve(gen); // 执行 onFulfilled 方法 onFulfilled(); function onFulfilled(res) { var ret; try { // 执行 gen 的 next 方法 ret = gen.next(res); } catch (e) { return reject(e); } // 并将这个值传入 next 函数 next(ret); } function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { // 如果 gen 执行完毕, ret.done 变为 true ,那么这个 promise 的实例 // 的状态自然变成了 resolved if (ret.done) return resolve(ret.value); var value = toPromise.call(ctx, ret.value); // 将 value 重新包装成一个 promise 实例 // 新返回的 promise 实例的 resolve 方法设置为 onFulfilled 函数,再次执行 next 方法, 从而实现了自动调用 generator 实例的 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 实例的 next 方法。那么接下来看看 co 是怎么把一个值转化为一个 promise 实例。
function toPromise(obj) { if (!obj) return obj; // 如果传入的 obj 是假值,返回这个假值 如 undefined , false, null if (isPromise(obj)) return obj; // 如果是 Promise 实例,返回这个 promise 实例 if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 如果是 generator 函数或者 一个generator if ("function" == typeof obj) return thunkToPromise.call(this, obj); // 如果是 thunk 函数 if (Array.isArray(obj)) return arrayToPromise.call(this, obj); // 如果是一个数组 if (isObject(obj)) return objectToPromise.call(this, obj); // 如果是一个 plain object return obj; // 如果是原始值,则返回这个原始值。 }
那么每个函数依次看下去。
function thunkToPromise(fn) { var ctx = this; // 保存函数上下文对象 // 返回一个 promise 实例 return new Promise(function (resolve, reject) { // 执行传入的 thunk 函数 // thunk 函数接受一个 回调函数 作为参数 fn.call(ctx, function (err, res) { // 如果 thunk 函数运行错误 // promise 实例的 变为 rejected 状态,执行 reject 函数,也就是 co 函数内定义的 onRejected 函数,下同 if (err) return reject(err); // 获得多余参数 if (arguments.length > 2) res = slice.call(arguments, 1); // promise 状态变为 resolved ,执行 resolve 函数,也就是 onFulfilled 函数 resolve(res); }); }); }
所以,总结一下就是说,如果 generator 里 yield 后面是一个 thunk 函数, 这个 thunk 函数接受一个回调参数作为参数,co 在这个回调函数里定义了何时将 promise 的状态变为 resolved 或者 rejected ,
function arrayToPromise(obj) { // Promise.all 方法返回一个 新的 promise 实例 // 如果 obj 是一个数组,把每个元素包装成一个 promise 实例 // 如果每一个 promise 如果都变为 resolved 状态 // 那么返回的新的 promise 实例的状态变为 resloved 状态 // 传给 resolve 函数的参数为之前每个 promise 的返回值所组成的数组 return Promise.all(obj.map(toPromise, this)); }
同样,如果 obj 是一个数组,也就是 yield 语句后面的表达式的值为一个数组,那么就执行 Promise.all 方法, 将数组的每一项都变成一个 promise 实例。
具体方法如下:
使用 toPromise 方法将 obj 数组中的每一项都包装成一个 promise 实例
如果上一步中的数组中有元素不是 promise 实例,Promise.all 方法将调用 Promise.resolve 方法,将其转化为 promise 实例。
Promise.all 方法返回一个新的 promise 实例。
只有 promise 实例数组中的所有实例的状态都变为 resolved 状态时,这个新的 promise 实例的状态才会变成 resolved。只要数组中有一个 promise 实例的状态变为 rejected ,新的promise 实例状态也马上变为 rejected 。
当返回的新的 promise 实例状态变为 resolved 时,传入其 resolve 函数的参数为之前数组中每个 promise 实例调用 resolve 函数的返回值组成的数组。如果返回的新的 promise 的状态变为 rejected ,那么传给 reject 函数的参数为数组中的 promise 实例最先变为 rejected 状态的那一个执行 reject 函数的返回值。
真绕口,多看几遍应该就能理解了。
最后来看看如果 ret.value 如果是一个对象,co 模块是怎么样把它变成一个 promise 实例的。
function objectToPromise(obj){ // 定义一个空对象 var results = new obj.constructor(); // 获取 obj 的全部属性 var keys = Object.keys(obj); // 用于盛放 每个属性值生成的对应的 promise 实例 var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); // 根据属性值生成一个 promise 实例 if (promise && isPromise(promise)) defer(promise, key); else results[key] = obj[key]; } // 通过一个 promise.all 方法返回一个新的实例 return Promise.all(promises).then(function () { return results; // 将 results 作为 onFulfilled 函数的参数 }); // 函数的作用 // 给 promise 添加 resolve 函数 // 并且把这个 promise 实例推入 promises 数组 function defer(promise, key) { // predefine the key in the result results[key] = undefined; promises.push(promise.then(function (res) { results[key] = res; // 定义promise 实例的 resolve 函数 })); } }总结
分析完 co 的整个源码总结一下整个执行的过程。首先,co 函数接受一个 generator 函数,并且在 co 函数内部执行,生成一个 generator 实例。调用 generator 的 next 方法, 对生成的对象的 value 属性值使用 toPromise 方法,生成一个 promise 实例,当这个 promise 实例的状态变为 resolved 时,执行 onFulfilled 方法,再次对 generator 实例执行 next 方法,然后重复整个过程。如果出现错误,则执行这个 promise 实例定义的 reject 函数即 onRejected 方法。
以上即实现了将异步过程同步化。
最后欢迎 star
https://github.com/zengxiaotao/zengxiaotao.github.io
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/91339.html
摘要:返回的结果是一个对象,类似于表示本次后面执行之后返回的结果。对象用于一个异步操作的最终完成或失败及其结果值的表示简单点说就是处理异步请求。源码分析主要脉络函数调用后,返回一个实例。参考链接解释对象的用法的源码及其用法 本文始发于我的个人博客,如需转载请注明出处。为了更好的阅读体验,可以直接进去我的个人博客看。 前言 知识储备 阅读本文需要对Generator和Promise有一个基本的...
摘要:新闻热点国内国外,前端最新动态发布近日,正式发布新版本中提供了一系列的特性与问题修复。而近日正式发布,其能够帮助开发者快速构建应用。 前端每周清单第 10 期:Firefox53、React VR发布、JS测试技术概述、Microsoft Edge现代DOM树构建及性能之道 为InfoQ中文站特供稿件,首发地址为这里;如需转载,请与InfoQ中文站联系。从属于笔者的 Web 前端入门...
摘要:前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。对该漏洞的综合评级为高危。目前,相关利用方式已经在互联网上公开,近期出现攻击尝试爆发的可能。 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢...
摘要:前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。它能够为我们提供类似于预处理器命名空间等多方面的辅助。 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为新闻热点、开发教程、工程实践、深度阅读、开源项目、巅峰人生等栏目。欢迎关注【前端之巅】微信公众号(ID:f...
阅读 2468·2021-09-23 11:21
阅读 1865·2021-09-22 15:15
阅读 950·2021-09-10 11:27
阅读 3417·2019-08-30 15:54
阅读 629·2019-08-30 15:52
阅读 1313·2019-08-30 15:44
阅读 2328·2019-08-29 15:06
阅读 2946·2019-08-28 18:21