Genertor 函数
比较容易迷惑的点:
function *foo(x) {
let y = 2 * (yield (x + 1))
let z = yield (y / 3)
return (x + y + z)
}
let it = foo(5)
console.log(it.next()) // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
- let it = foo(5) 返回一个迭代器
- console.log(it.next())
函数暂停在
yield (x + 1),返回5+1等于6 - console.log(it.next(12))
传参12,上一个yield的返回值为12,
let y = 2 * 12,所以 value = 8 - console.log(it.next(13))
执行到这一步时,
z = 13,x+y+z = 5+24+13 = 42;
Genertor thunk版本异步
- 什么是thunk函数? 比如说:
function isType(type) {
return (val)=>{
return Object.prototype.toString.call(val) === `[object ${type}]`
}
}
let isString = isType('String');
let isNumber = isType('Number');
console.log(isString('abc'));
console.log(isNumber(123));
isType这样的函数我们称之为thunk函数,根据特定需求,返回定制化的函数,去完成特定的功能。
以文件操作为例,看Genertor如何用于异步,
function readTunnk(fileName) {
return (callback)=>{
fs.readFile(fileName, callback)
}
}
readTunnk是一个thunk函数,目的是为读写的每一个文件绑定定制化回调函数,异步的核心一环就是绑定回调函数,这样就是Genertor和异步关联起来了。next执行机制使thunk函数返回了定制化函数,并绑定在next的执行结果里边,这样关联起来。
然函数完整的执行下
const gen = function* (params) {
let file1 = yield readTunnk('001.txt');
console.log(file1.toString());
let file2 = yield readTunnk('002.txt');
console.log(file2.toString());
}
let g = gen();
/*
第一步调用next,开始执行,next返回的value是一个定制化的函数,
需要一个函数作为参数,座位读取文件后的回调函数
第二步,传递data1作为参数传入,程序继续执行
第三步,同理data2作为参数传入
*/
g.next().value((err, data1)=>{
g.next(data1).value((err,data2)=>{
g.next(data2);
})
})
打印结果如下
001.txt的内容
002.txt的内容
如果多层嵌套上边的就会很复杂,封装下代码
/*
如果res.done == false
就需要再次调用next
直到 res.done == true,
这不就是迭代吗
*/
const runGen = function (g) {
const next = function (err,data) {
const res = g.next(data);
if(res.done) return;
res.value(next);
}
}
runGen(gen());
这就是thunk函数的Genertor异步版本。Promise当然也可以实现,怎么实现呢?
Genertor Promise版本异步
原理基本上一样的
const promiseGen = function (str) {
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(str);
}, 1000);
})
}
const gen = function* () {
let str1 = yield promiseGen('abc');
console.log(str1);
let str2 = yield promiseGen('def');
console.log(str2);
};
promiseGen函数,我们模拟异步一秒后决议str,是不是和thunk函数有点像,执行thunk函数返回一个定制化函数,执行promiseGen返回一个特定的promise,只不过是写法少有不同,
看下执行,
let g = gen();
// g.next().value等于定制化promise 然后调用then
// return g.next(str1).value 拿到 'abc'然后传参给下一个 yield
g.next().value
.then(str1=>{
return g.next(str1).value
})
.then(str2=>{
return g.next(str2)
})
查看输出
一秒后打印出了abc,隔一秒打印出了def
promise版本还是存在嵌套过多的问题,再来封装,和thunk函数封装基本一样
let g = gen();
const runGen = function (g) {
const next = function(data){
const res = g.next(data);
if(res.done) return;
res.value.then((data2)=>{
next(data2)
})
}
next();
};
runGen(g);
采用co库
我们针对thunk函数和promise两种Genertor异步操作的一次性执行做了封装,但实际场景已经有了成熟的工具包,就是co库,其核心代码我们已经写过了,只不过源码会对各种边界情况做处理,食用方法:
const co = require('co');
let g = gen();
co(g).then(res =>{
console.log(res);
})
好了已经完成了,真不愧是天生一对。
async/await
async/await被称为异步的终极解决方案,来看下用法
const promiseGen = function (str) {
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(str);
}, 1000);
})
}
async function logFunc(params) {
const str1 = await promiseGen('abc');
console.log(str1);
const str2 = await promiseGen('def');
console.log(str2);
}
logFunc();
看下输出
真不愧是终极解决方案,没有那么多封装,而且书写格式符合人们大脑的思考方式同步运行,真的是厉害。
接下来我们深入一点,有没有发现这个和我们实现的自执行的Genertor函数有点像
runGen 和 yield
实际上async和await就是上边我们对Genertor的封装,是一个语法糖;
参考文章: