[自己动手写]使用Generator的异步库
栏目: JavaScript · 发布时间: 5年前
内容简介:使用输出为:如果代码为:
使用 function*
的形式申明Generator函数,并使用 yield
关键字返回值;执行Generator函数会返回一个遍历器对象,遍历器对象带有 next
方法,执行后返回的值包含 value
与 done
属性:
const g = function* g() { yield 'hello'; yield 'world'; } const iterable = g(); let v; do { v = iterable.next(); console.log(v.value, v.done); } while(!v.done) 复制代码
输出为:
hello false world false undefined true 复制代码
如果代码为:
const g = function* g() { yield 'hello'; return 'world'; } 复制代码
则输出为:
hello false world true 复制代码
Generator中可以使用 for
循环、 while
循环等:
const g = function* g() { const arr = ['hello', 'world'] for (let item of arr) { yield item } } 复制代码
我们可以用 for of
遍历迭代器:
for (let item of g()) { console.log(item) } // hello // world 复制代码
可以通过设置 [Symbol.iterator]
来创造迭代器:
const iterable = { [Symbol.iterator]: function* () { const arr = ['hello', 'world'] for (let item of arr) { yield item } } }; for (let item of iterable) { console.log(item) } 复制代码
Generator可以通过 next
传入参数(从第二次调用 next
起,后面会解释为什么从第二次起):
const g = function* g() { const arr = []; let v while(v = yield arr) { arr.push(v) } } const iterable = g(); console.log(iterable.next().value); // [] console.log(iterable.next(1).value); // [1] console.log(iterable.next(2).value); // [1, 2] 复制代码
可以使用 throw
来抛出错误(在 throw
前确保先执行一次 next
):
const g = function* g() { const arr = []; let v while(true) { try { v = yield arr arr.push(v) } catch (err) { console.log('err', err) } } } const iterable = g(); console.log(iterable.next().value); console.log(iterable.next(1).value); iterable.throw('whoops'); console.log(iterable.next(2).value); // [] // [1] // err, whoops // [1, 2] 复制代码
也可以由Generator抛出错误,外部捕获:
const g = function* g() { const hasErr = yield 1 if (hasErr) throw 'whoops' yield 2 } const iterable = g(); iterable.next(); try { iterable.next(true); } catch(err) { console.log(err); } 复制代码
可以用 return
提前结束Generator:
const g = function* g() { yield 1; yield 2; yield 3; } var iterable = g(); console.log(iterable.next()) // { value: 1, done: false } console.log(iterable.return('foo')) // { value: 'foo', done: true } console.log(iterable.next()) // { value: undefined, done: true } 复制代码
return
会被 finally
捕获:
const g = function* g() { yield 1; try { yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } const iterable = g(); iterable.next() // { value: 1, done: false } iterable.next() // { value: 2, done: false } iterable.return(7) // { value: 4, done: false } iterable.next() // { value: 5, done: false } iterable.next() // { value: 7, done: true } 复制代码
Generator函数被称为“半协程”(semi-coroutine),只有Generator函数的调用者,才能将程序的执行权还给Generator函数。
执行过程
-
第一次
next
的执行,代码运行到第一个第一个yield
之前,返回yield
之后的值/表达式 -
第二次执行
next
,传入的参数将替换第一个yield
位置的语句,并向后执行,直到下一个yield
,返回值(因此第一次next
传入的值是被抛弃的;同理throw
之前也需要至少执行过一次next
) -
按照第二次起的逻辑,继续重复执行,直到
return
或不再yield
在执行 yield
之后,Generator等待外部调用 next
再执行之后的代码,在这个过程中,可以执行一些异步操作,再调用 next
将结果返回Generator同时将程序的执行权还给Generator并继续执行,这也是可以用Generator模拟类似async/await的同步写法的原因。
generator与异步
因此,我们可以进行这样的操作:
const getUserId = (token) => { setTimeout(() => { iterable.next(parseInt(token.split(' ')[1], 10)); }, 0); }; const getUserInfo = (userId) => { setTimeout(() => { iterable.next({ userId }); }, 0); }; const g = function* (token) { const userId = yield getUserId(token); console.log(userId); const data = yield getUserInfo(userId); console.log(data); return data; }; const iterable = g('Bearer 123'); iterable.next(); 复制代码
还可以使用 co
库:
const co = require('co'); const getUserId = (token) => { return Promise.resolve(parseInt(token.split(' ')[1], 10)); }; const getUserInfo = (userId) => { return Promise.resolve({ userId }); }; const fn = co.wrap(function* (token) { const userId = yield getUserId(token); const data = yield getUserInfo(userId); return data; }); fn('Bearer 123').then(data => { console.log(data); }); 复制代码
自己动手实现 co
库
这里我们实现一个简化版的 co
库,我们所要做的,是实现如下过程的自动化:
-
调用Generator的
next
方法 -
在
next
返回的Promise
完成时,用Promise的返回值再次调用next
方法 -
反复执行第二步,直到
next
返回的done
为true
以下是实现代码:
co
函数将返回一个Promise:
const co = function (fn, ...args) { return new Promise((resolve, reject) => { ... }); }; 复制代码
首先我们调用传入的 fn
来生成带有 next
方法的 generator
实例:
gen = fn.apply(this, args); 复制代码
我们需要实现 next
函数:在调用 gen.next
方法之后,我们会获得带有 value
和 done
的结果,这个结果作为参数调用 next
;如果 done
为 true
,则直接 resolve(value)
;否则, value
应该是一个 Promise
实例,我们通过 value.then
注册回调。
在 value.then
的回调中,我们将使用(Generator外部的异步或同步方法的)返回的值继续调用 gen.next
,并用其返回的结果继续调用 next
进行处理:
const onFulfilled = function (res) { let ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); }; const next = function (p) { const { done, value } = p; if (done) { resolve(value); } else { (value.then ? value : Promise.resolve(value)).then(onFulfilled, reject); } }; 复制代码
最后,别忘了调用一次 gen.next
来启动整个过程:
onFulfilled(); 复制代码
这里判断 value.then
并使用 Promise.resolve
,是为了兼容外部调用可以是同步方法的情况,例如:
const getUserId = (token) => { return parseInt(token.split(' ')[1], 10); }; 复制代码
最后我们实现 co.wrap
函数,这里进行了柯里化:
co.wrap = function (fn) { return function(...args) { return co.call(this, fn, ...args); }; }; 复制代码
完整示例代码
const co = function (fn, ...args) { return new Promise((resolve, reject) => { gen = fn.apply(this, args); const onFulfilled = function (res) { let ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); }; const next = function (p) { const { done, value } = p; if (done) { resolve(value); } else { (value.then ? value : Promise.resolve(value)).then(onFulfilled, reject); } }; onFulfilled(); }); }; co.wrap = function (fn) { return function(...args) { return co.call(this, fn, ...args); }; }; // demo const getUserId = (token) => { return parseInt(token.split(' ')[1], 10); }; const getUserInfo = (userId) => { return Promise.resolve({ userId }); }; const fn = co.wrap(function* (token) { const userId = yield getUserId(token); const data = yield getUserInfo(userId); return data; }); fn('Bearer 123').then(data => { console.log(data); }); 复制代码
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Qt 5.9 C++开发指南
王维波、栗宝鹃、侯春望 / 人民邮电出版社 / 2018-5-1 / 89.00元
本书以Qt 5.9 LTS版本为开发平台,详细介绍了Qt C++开发应用程序的技术,包括Qt应用程序的基本架构、信号与槽工作机制、图形显示的Graphics/View架构、数据编辑和显示的Model/View架构、对话框和多窗口的设计与调用方法等,介绍了常用界面组件、文件读写、绘图、图表、数据可视化、数据库、多线程、网络和多媒体等模块的使用。每个编程主题都精心设计了完整的实例程序。 通过阅读......一起来看看 《Qt 5.9 C++开发指南》 这本书的介绍吧!