大家好,我是若川。这是 源码共读活动《1个月,200+人,一起读了4周源码》 第四期,纪年小姐姐的第四次投稿。纪年小姐姐通过本次学习提早接触到generator,协程概念,了解了async/await函数的原理等。

第四期是 学习 koa 源码的整体架构,浅析koa洋葱模型原理和co原理中的co原理。不知不觉,源码共读已经进行了一个月,有些小伙伴表示对面试和工作很有帮助,学完立马能用。如果你也感兴趣可以加我微信 ruochuan12参加。


1. 前言

这周看的是 co 的源码,我对 co 比较陌生,没有了解和使用过。因此在看源码之前,我希望能大概了解 co 是什么,解决了什么问题。

2. 简单了解 co

先看了 co 的 GitHub,README 是这样介绍的:

Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.

看起来有点懵逼,又查了一些资料,大多说 co 是用于 generator 函数的自动执行。generator 是 ES6 提供的一种异步编程解决方案,它最大的特点是可以控制函数的执行。

2.1 关于 generator

说到异步编程,我们很容易想到还有 promise,async 和 await。它们有什么区别呢?先看看 JS 异步编程进化史:callback -> promise -> generator -> async + await

JS 异步编程

再看看它们语法上的差异:

Callback Promise Generator async + await + Promise
ajax(url, () => {}) Promise((resolve,reject) => { resolve() }).then() function* gen() { yield 1} async getData() {  await fetchData() }

关于 generator 的学习不在此篇幅详写了,需要了解它的概念和语法。

3. 学习目标

经过简单学习,大概明白了 co 产生的背景,因为 generator 函数不会自动执行,需要手动调用它的 next() 函数,co 的作用就是自动执行 generator 的 next() 函数,直到 done 的状态变成 true 为止。

那么我这一期的学习目标:

1)解读 co 源码,理解它是如何实现自动执行 generator

2)动手实现一个简略版的 co

4. 解读 co 源码

co 源码地址:https://github.com/tj/co

4.1 整体架构

从 README 中,可以看到是如何使用 co :

co(function* () {var result = yield Promise.resolve(true);return result;
}).then(function (value) {console.log(value);
}, function (err) {console.error(err.stack);
});

从代码可以看到它接收了一个 generator 函数,返回了一个 Promise,这部分对应的源码如下。

function co(gen) {var ctx = this;// 获取参数var args = slice.call(arguments, 1);// 返回一个 Promisereturn new Promise(function(resolve, reject) {// 把 ctx 和参数传递给 gen 函数if (typeof gen === 'function') gen = gen.apply(ctx, args);// 判断 gen.next 是否函数,如果不是直接 resolve(gen)if (!gen || typeof gen.next !== 'function') return resolve(gen);// 先执行一次 nextonFulfilled();// 实际上就是执行 gen.next 函数,获取 gen 的值function onFulfilled(res) {var ret;try {ret = gen.next(res);} catch (e) {return reject(e);}next(ret);return null;}// 对 gen.throw 的处理function onRejected(err) {var ret;try {ret = gen.throw(err);} catch (e) {return reject(e);}next(ret);}// 实际处理的函数,会递归执行,直到 ret.done 状态为 truefunction next(ret) {// 如果生成器的状态 done 为 true,就 resolve(ret.value),返回结果if (ret.done) return resolve(ret.value);// 否则,将 gen 的结果 value 封装成 Promisevar value = toPromise.call(ctx, ret.value);// 判断 value 是否 Promise,如果是就返回 thenif (value && isPromise(value)) return value.then(onFulfilled, onRejected);// 如果不是 Promise,Rejectedreturn onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '+ 'but the following object was passed: "' + String(ret.value) + '"'));}});
}

看到这里,我产生了一个疑问:Promise + then 也可以处理异步编程,为什么 co 的源码里要把 Promise + generator 结合起来呢,为什么要这样做?直到我搞懂了 co 的核心目的,它使 generator 和 yield 的语法更趋向于同步编程的写法,引用阮一峰的网络日志中的一句话就是:

异步编程的语法目标,就是怎样让它更像同步编程。

可以看一个 Promise + then 的例子:

function getData() {return new Promise(function(resolve, reject) {resolve(1111)})
}
getData().then(function(res) {// 处理第一个异步的结果console.log(res);// 返回第二个异步return Promise.resolve(2222)
})
.then(function(res) {// 处理第二个异步的结果console.log(res)
})
.catch(function(err) {console.error(err);
})

如果有多个异步处理就会需要写多少个 then 来处理异步之间可能存在的同步关系,从以上的代码可以看到 then 的处理是一层一层的嵌套。如果换成 co,在写法上更优雅也更符合日常同步编程的写法:

co(function* () {try {var result1 = yield Promise.resolve(1111)// 处理第一个异步的结果console.log(result1);// 返回第二个异步var result2 = yield Promise.resolve(2222)// 处理第二个异步的结果console.log(result2)} catch (err) {console.error(err)}
});

4.2 分析 next 函数

源码的 next 函数接收一个 gen.next() 返回的对象 ret 作为参数,形如{value: T, done: boolean},next 函数只有四行代码。

第一行:if (ret.done) return resolve(ret.value); 如果 ret.done 为 true,表明 gen 函数到了结束状态,就 resolve(ret.value),返回结果。

第二行:var value = toPromise.call(ctx, ret.value); 调用 toPromise.call(ctx, ret.value) 函数,toPromise 函数的作用是把 ret.value 转化成 Promise 类型,也就是用 Promise 包裹一层再 return 出去。

function toPromise(obj) {// 如果 obj 不存在,直接返回 objif (!obj) return obj;// 如果 obj 是 Promise 类型,直接返回 objif (isPromise(obj)) return obj;// 如果 obj 是生成器函数或遍历器对象, 就递归调用 co 函数if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);// 如果 obj 是普通的函数类型,转换成 Promise 类型函数再返回if ('function' == typeof obj) return thunkToPromise.call(this, obj);// 如果 obj 是一个数组, 转换成 Promise 数组再返回if (Array.isArray(obj)) return arrayToPromise.call(this, obj);// 如果 obj 是一个对象, 转换成 Promise 对象再返回if (isObject(obj)) return objectToPromise.call(this, obj);// 其他情况直接返回return obj;
}

第三行:if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 如果 value 是 Promise 类型,调用 onFulfilled 或 onRejected,实际上是递归调用了 next 函数本身,直到 done 状态为 true 或 throw error。

第四行:return onRejected(...) 如果不是 Promise,直接 Rejected。

5. 实践

虽然解读了 co 的核心代码,看起来像是懂了,实际上很容易遗忘。为了加深理解,结合上面的 co 源码和自己的思路动手实现一个简略版的 co。

5.1 模拟请求

function request() {return new Promise((resolve) => {setTimeout(() => {resolve({data: 'request'});}, 1000);});
}
// 用 yield 获取 request 的值
function* getData() {yield request()
}
var g = getData()
var {value, done} = g.next()
// 间隔1s后打印 {data: "request"}
value.then(res => console.log(res))

5.2 模拟实现简版 co

核心实现:

1)函数传参

2)generator.next 自动执行

function co(gen) {// 1. 传参var ctx = this;const args = Array.prototype.slice.call(arguments, 1);gen = gen.apply(ctx, args);return new Promise(function(resolve, reject) {// 2. 自动执行 nextonFulfilled()function onFulfilled (res) {var ret = gen.next(res);next(ret);}function next(ret){if (ret.done) return resolve(ret.value);// 此处只处理 ret.value 是 Promise 对象的情况,其他类型简略版没处理var promise = ret.value;// 自动执行promise && promise.then(onFulfilled);}})
}// 执行
co(function* getData() {var result = yield request();// 1s后打印 {data: "request"}console.log(result)
})

6. 感想

对我来说,学习一个新的东西(generator)花费的时间远远大于单纯阅读源码的时间,因为需要了解它产生的背景,语法,解决的问题以及一些应用场景,这样在阅读源码的时候才知道它为什么要这样写。

读完源码,我们会发现,其实 co 就是一个自动执行 next() 的函数,而且到最后我们会发现 co 的写法和我们日常使用的 async/await 的写法非常相像,因此也不难理解【async/await 实际上是对 generator 封装的一个语法糖】这句话了。

// co 写法
co(function* getData() {var result = yield request();// 1s后打印 {data: "request"}console.log(result)
})
// async await 写法
(async function getData() {var result = await request();// 1s后打印 {data: "request"}console.log(result)
})()

不得不说,阅读源码的确是一个开阔视野的好方法,如果不是这次活动,我可能还要晚个大半年才接触到 generator,接触协程的概念,了解到 async/await 实现的原理,希望能够继续坚持下去~

最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。


推荐阅读

1个月,200+人,一起读了4周源码
我读源码的经历

老姚浅谈:怎么学JavaScript?

我在阿里招前端,该怎么帮你(可进面试群)

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》多篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,活跃在知乎@若川,掘金@若川。致力于分享前端开发经验,愿景:帮助5年内前端人走向前列。

识别方二维码加我微信、拉你进源码共读

今日话题

略。欢迎分享、收藏、点赞、在看我的公众号文章~

面试官问 async、await 函数原理是在问什么?相关推荐

  1. 面试让写一个“bind”函数,详解五层bind函数进阶写法,带你写出一个让面试官满意的 “bind” 函数

    文章目录 手写bind函数 第一层 - 绑定在原型上的方法 第二层 - 改变this的指向 第三层 - 支持柯里化 第四层 - 考虑 new 的调用 第五层 - 保留函数原型 总结最终版bind函数 ...

  2. 面试官:说一下HashMap原理,循环链表是如何产生的

    Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据.众所周知 HashMap 底层是基于 数组 + 链表 组成的,不过在 JDK1.7 和 1.8 中具体实现稍有 ...

  3. promise 、async/await 的原理及实现

    前言 事件循环机制 由于 javascript 引擎是采用单线程运行机制,执行耗时过大的操作时会造成页面的阻塞,为了解决页面的阻塞问题,js 将任务分为 同步任务.异步任务,随之而来的是异步带来的执行 ...

  4. 我向面试官讲解了hashmap底层原理,他对我竖起了大拇指

    前言: 正值金九银十的黄金招聘期,大家都准备好了吗?HashMap是程序员面试必问的一个知识点,其内部的基本实现原理是每一位面试者都应该掌握的,只有真正地掌握了 HashMap的内部实现原理,面对面试 ...

  5. Java面试官:java的跨平台原理

    京东一面凉经 object的方法,7大方法 synchronized方法讲解 synchronized方法实现原理 volatile关键字的原理 锁的分类 偏向锁讲解 NoClassDefFoundE ...

  6. 阿里二面,面试官:说说 Java CAS 原理?

    声明:本文禁止转载 1. 什么是 CAS? 2. CAS 基本原理 3. CAS 在 Java 语言中的应用 4. CAS 的问题 4.1. 典型 ABA 问题 4.2. 自旋开销问题 4.3. 只能 ...

  7. 【面试题】面试官: MySQL的主从原理你说一下

    前言 mysql 主从原理是面试时候必问的面试题,被面试到的概率达到了90%,所以需要提前准备. 推荐准备指数: 五颗星 通常解法 从库生成两个线程,一个I/O线程,一个SQL线程, i/o线程去请求 ...

  8. 单例模式双重校验锁_被面试官虐过之后,他轻蔑的问我:你还说你了解单例模式吗?...

    单例,大家肯定都不陌生,这是Java中很重要的一个设计模式.其实单例模式看上去简单,实际上却有很多容易被忽视的地方,因为他涉及到一些线程安全的问题,稍不留神就可能入坑. 本文,就通过一次面试经历来深入 ...

  9. rabbitmq 拉取消息太慢_面试官:消息队列这些我都要问

    作者:mousycoder segmentfault.com/a/1190000021054802 消息队列连环炮 项目里怎么样使用 MQ 的? 为什么要使用消息队列? 消息队列有什么优点和缺点? k ...

最新文章

  1. Bitcoin ABC近半数节点已经完成了版本更迭
  2. 请教各位高手!手机要如何访问电脑的tomcat呢!
  3. SAP Commerce的路由实现(Route Implementation)
  4. Dinosaur Run - Dinosaur world Games
  5. 准确性 敏感性 特异性_如何掌握类型特异性的艺术
  6. linux——线程通信(2)
  7. 建立丰富多彩的toast的简单实例
  8. JenKins自动化构建部署流程参考
  9. 百面机器学习 #3 经典算法:01-3 核函数支撑向量机SVM
  10. 用C#实现MVC(Model View Control)模式介绍
  11. mysql-数据备份操作
  12. 如何在Windows下强制git使用LF而不是CR + LF?
  13. 从Java新特性看Java的未来
  14. FSM实例——按键消抖及状态检测
  15. 计算机信息处理技术的易混淆知识点,计算机等级考试二级VisualFoxPro备考策略、考试题型与解题技巧与易混淆的知识点...
  16. 分布式系统故障容灾治理总结
  17. 百度指数对网站优化有什么作用
  18. oracle elsif和else if,ORACLE ELSIF 与 ELSE IF
  19. 隐私数据保护的两大途径
  20. yocto系列讲解[理论篇]56 - poky下目录结构

热门文章

  1. vscode 里 Import “numpy“ count not be resolved
  2. android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升
  3. 使用git上传代码到github远程仓库
  4. Markdown 基础学习
  5. 浅谈Junit4和TestNG中的参数化测试
  6. 邮件服务器“单点登录”功能
  7. 人民币小写金额转大写金额
  8. 将DataTable的内容以EXCEl的形式导出到本地
  9. 破解MS Word 的只读密码限制
  10. python is beautiful_Python list 和 str 互转