这周看的是 co 的源码,我对 co 比较陌生,没有了解和使用过。因此在看源码之前,我希望能大概了解 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 提供的一种异步编程解决方案,它最大的特点是可以控制函数的执行。
说到异步编程,我们很容易想到还有 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 的学习不在此篇幅详写了,需要了解它的概念和语法。
经过简单学习,大概明白了 co 产生的背景,因为 generator 函数不会自动执行,需要手动调用它的 next() 函数,co 的作用就是自动执行 generator 的 next() 函数,直到 done 的状态变成 true 为止。
那么我这一期的学习目标:
1)解读 co 源码,理解它是如何实现自动执行 generator
2)动手实现一个简略版的 co
co 源码地址:https://github.com/tj/co
从 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);
// 返回一个 Promise
return 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);
// 先执行一次 next
onFulfilled();
// 实际上就是执行 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 状态为 true
function next(ret) {
// 如果生成器的状态 done 为 true,就 resolve(ret.value),返回结果
if (ret.done) return resolve(ret.value);
// 否则,将 gen 的结果 value 封装成 Promise
var value = toPromise.call(ctx, ret.value);
// 判断 value 是否 Promise,如果是就返回 then
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
// 如果不是 Promise,Rejected
return 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)
}
});
源码的 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 不存在,直接返回 obj
if (!obj) return obj;
// 如果 obj 是 Promise 类型,直接返回 obj
if (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。
虽然解读了 co 的核心代码,看起来像是懂了,实际上很容易遗忘。为了加深理解,结合上面的 co 源码和自己的思路动手实现一个简略版的 co。
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))
核心实现:
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. 自动执行 next
onFulfilled()
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)
})
对我来说,学习一个新的东西(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 实现的原理,希望能够继续坚持下去
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/bkazC9WqEVgvqsyhuvXxzA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。