面试官问 async、await 函数原理是在问什么?
大家好,我是若川。这是 源码共读活动《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
再看看它们语法上的差异:
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 函数原理是在问什么?相关推荐
- 面试让写一个“bind”函数,详解五层bind函数进阶写法,带你写出一个让面试官满意的 “bind” 函数
文章目录 手写bind函数 第一层 - 绑定在原型上的方法 第二层 - 改变this的指向 第三层 - 支持柯里化 第四层 - 考虑 new 的调用 第五层 - 保留函数原型 总结最终版bind函数 ...
- 面试官:说一下HashMap原理,循环链表是如何产生的
Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据.众所周知 HashMap 底层是基于 数组 + 链表 组成的,不过在 JDK1.7 和 1.8 中具体实现稍有 ...
- promise 、async/await 的原理及实现
前言 事件循环机制 由于 javascript 引擎是采用单线程运行机制,执行耗时过大的操作时会造成页面的阻塞,为了解决页面的阻塞问题,js 将任务分为 同步任务.异步任务,随之而来的是异步带来的执行 ...
- 我向面试官讲解了hashmap底层原理,他对我竖起了大拇指
前言: 正值金九银十的黄金招聘期,大家都准备好了吗?HashMap是程序员面试必问的一个知识点,其内部的基本实现原理是每一位面试者都应该掌握的,只有真正地掌握了 HashMap的内部实现原理,面对面试 ...
- Java面试官:java的跨平台原理
京东一面凉经 object的方法,7大方法 synchronized方法讲解 synchronized方法实现原理 volatile关键字的原理 锁的分类 偏向锁讲解 NoClassDefFoundE ...
- 阿里二面,面试官:说说 Java CAS 原理?
声明:本文禁止转载 1. 什么是 CAS? 2. CAS 基本原理 3. CAS 在 Java 语言中的应用 4. CAS 的问题 4.1. 典型 ABA 问题 4.2. 自旋开销问题 4.3. 只能 ...
- 【面试题】面试官: MySQL的主从原理你说一下
前言 mysql 主从原理是面试时候必问的面试题,被面试到的概率达到了90%,所以需要提前准备. 推荐准备指数: 五颗星 通常解法 从库生成两个线程,一个I/O线程,一个SQL线程, i/o线程去请求 ...
- 单例模式双重校验锁_被面试官虐过之后,他轻蔑的问我:你还说你了解单例模式吗?...
单例,大家肯定都不陌生,这是Java中很重要的一个设计模式.其实单例模式看上去简单,实际上却有很多容易被忽视的地方,因为他涉及到一些线程安全的问题,稍不留神就可能入坑. 本文,就通过一次面试经历来深入 ...
- rabbitmq 拉取消息太慢_面试官:消息队列这些我都要问
作者:mousycoder segmentfault.com/a/1190000021054802 消息队列连环炮 项目里怎么样使用 MQ 的? 为什么要使用消息队列? 消息队列有什么优点和缺点? k ...
最新文章
- Bitcoin ABC近半数节点已经完成了版本更迭
- 请教各位高手!手机要如何访问电脑的tomcat呢!
- SAP Commerce的路由实现(Route Implementation)
- Dinosaur Run - Dinosaur world Games
- 准确性 敏感性 特异性_如何掌握类型特异性的艺术
- linux——线程通信(2)
- 建立丰富多彩的toast的简单实例
- JenKins自动化构建部署流程参考
- 百面机器学习 #3 经典算法:01-3 核函数支撑向量机SVM
- 用C#实现MVC(Model View Control)模式介绍
- mysql-数据备份操作
- 如何在Windows下强制git使用LF而不是CR + LF?
- 从Java新特性看Java的未来
- FSM实例——按键消抖及状态检测
- 计算机信息处理技术的易混淆知识点,计算机等级考试二级VisualFoxPro备考策略、考试题型与解题技巧与易混淆的知识点...
- 分布式系统故障容灾治理总结
- 百度指数对网站优化有什么作用
- oracle elsif和else if,ORACLE ELSIF 与 ELSE IF
- 隐私数据保护的两大途径
- yocto系列讲解[理论篇]56 - poky下目录结构
热门文章
- vscode 里 Import “numpy“ count not be resolved
- android 智能指针的学习先看邓凡平的书扫盲 再看前面两片博客提升
- 使用git上传代码到github远程仓库
- Markdown 基础学习
- 浅谈Junit4和TestNG中的参数化测试
- 邮件服务器“单点登录”功能
- 人民币小写金额转大写金额
- 将DataTable的内容以EXCEl的形式导出到本地
- 破解MS Word 的只读密码限制
- python is beautiful_Python list 和 str 互转