基本概念
- 异步编程解决方案
定义Generator函数
function* f() {
}
-
特征
-
function关键字与函数名之间有一个星号 -
函数体内部可用
yield关键字,定义不同的内部状态(yield在英语里的意思就是“产出”)
-
执行Generator函数
-
执行 Generator 函数,函数本身不会执行,而是会返回一个
遍历器对象-
该对象也是
可遍历的,因为在其原型链上也具有Symbol.iterator方法 -
该方法返回的对象就是该遍历器对象自身
-
function* f() {
console.log(1)
}
let a = f()
a[Symbol.iterator]() === a // true
-
Generator函数返回的对象也可以被遍历,相当于每次调用此对象
next()的value来作为遍历结果 -
只有执行该遍历器对象的
next()方法,Generator函数才会执行
function* f() {
console.log(1)
}
let a = f()
a.next() // 打印1 返回{value:undefined,done:true}
yield 和 yield*
- Generator函数中
yield关键字定义函数返回的遍历器对象每次next()后的value
function* f() {
yield 1
}
let a = f()
a.next() // 返回 {value: 1, done: false}
-
并且a每次执行
next(),都会在下一个yield处暂停 -
直到后面没有
yield关键字,则执行剩余代码,并且返回done:true
function* f() {
console.log('step1')
yield 1
console.log('step2');
yield 2
console.log('step3');
}
let a = f()
a.next() // 打印step1 返回 {value: 1, done: false}
a.next() // 打印step2 返回 {value: 2, done: false}
a.next() // 打印step3 返回 {value: undefined, done: true}
-
yield本身没有返回值,yield的返回值是下一次next()函数传入的值 -
next()方法作用-
执行本次
yield到下一个yield之间的代码 -
将形参的值传给本次
yield的返回值
-
-
next()和yield实现了函数内外控制权的转移
function* f() {
console.log('start');
let result = yield 1
console.log('result:',result);
}
let a = f()
yield*等同于遍历某个对象,并且yield每个结果
function* f() {
yield 'start'
yield* [1, 2, 3]
/*等同于*/
// for(let value of [1,2,3]){
// yield value
// }
yield 'end'
}
let a = f()
a.next() // 返回 {value: 'start', done: false}
a.next() // 返回 {value: 1, done: false}
a.next() // 返回 {value: 2, done: false}
a.next() // 返回 {value: 3, done: false}
a.next() // 返回 {value: 'end', done: false}
a.next() // 返回 {value: undefined, done: true}
Generator函数配合自动执行器
直接循环存在的问题
- 每次手动调用
next()很麻烦,写一个循环来执行next()
function* f() {
yield 1
console.log('完成1');
yield 2
console.log('完成2');
}
let it = f()
let done = false
while (!done){
done = it.next().done
}
- 如果
yield后面本身就是一个异步操作,就会有问题
function* f() {
yield readFile(file1)
console.log('耶,完成了1');
yield readFile(file2)
console.log('耶,完成了2');
}
let it = f()
let done = false
while (!done){
done = it.next().done
}
//耶,完成了1
//耶,完成了2
- 如果需求是让异步操作执行完毕后再执行
yield后面的代码,那么上述执行顺序就不符合需求
function* f() {
yield readFile(file1,function (err,data) {
console.log('读取到数据1:' + data)
})
console.log('耶,完成了1');
yield readFile(file2,function (err,data) {
console.log('读取到数据2:' + data)
})
console.log('耶,完成了2');
}
let it = f()
let done = false
while (!done){
done = it.next().done
}
//耶,完成了1
//耶,完成了2
//读取到数据1:111
//读取到数据2:222
Thunk函数
- Thunk 函数是指将多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数
// Thunk版本的readFile(单参数版本)
const {readFile} = require('fs')
const path = require('path')
const file1 = path.join(__dirname,'./text/1.txt')
const file2 = path.join(__dirname,'./text/2.txt')
let Thunk = function (fileName) {
return function (callback) {
return readFile(fileName, callback);
};
};
let readFileThunk = Thunk(file1);
readFileThunk(function(err,data){
console.log(String(data));
});
有一个
thunkify库可以方便的将api变成Thunk函数
自动执行器
-
写一个自动执行器
run函数-
每次将
it.next()的逻辑封装到nextStep()中 -
将
nextStep作为回调函数传给Thunk化后的读取文件函数
-
// Thunk版本的readFile(单参数版本)
const {readFile} = require('fs')
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
let Thunk = function (fileName) {
return function (callback) { //result.value
return readFile(fileName, callback);
};
};
function* f() {
let data1 = yield Thunk(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = yield Thunk(file2)
console.log('耶,完成了2,数据是' + data2);
}
function run(f) {
let it = f();
function nextStep(err, data) {
var result = it.next(data);
if (result.done) return;
result.value(nextStep); //执行readFile,并且把nextStep作为回调传入
}
nextStep();
}
run(f)
- 基于自动执行器,只要异步操作是Thunk函数或者返回Promise的情况下,写异步逻辑在形式上就如同写同步逻辑一样
co模块
-
co模块是对一个封装的更好的自动执行器
-
它支持
yield的类型,不光包含thunk函数,还有Promise对象,数组,对象,甚至Generator函数
const { promisify } = require("util");
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
const readFileP = promisify(readFile)
let Thunk = function (fileName) {
return function (callback) { //result.value
return readFile(fileName, callback);
};
};
/*Thunk*/
function* f() {
let data1 = yield Thunk(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = yield Thunk(file2)
console.log('耶,完成了2,数据是' + data2);
}
/*Promise*/
function* f() {
let data1 = yield readFileP(file1)
console.log('耶,完成了1,数据是' + data1);
let data2 = yield readFileP(file2)
console.log('耶,完成了2,数据是' + data2);
}
/*数组(并发)*/
function* f() {
let data = yield [readFileP(file1),readFileP(file2)]
console.log('耶,完成了,数据是' + data);
}
/*对象(并发)*/
function* f() {
let data = yield {data1:readFileP(file1),data2:readFileP(file2)}
console.log('耶,完成了,数据是' + JSON.stringify(data));
}
/*Generator函数*/
function* f() {
function* f1() {
return yield {data1:readFileP(file1),data2:readFileP(file2)}
}
let data = yield f1()
console.log('耶,完成了,数据是' + JSON.stringify(data));
}
co(f)
- 经过一个co模块执行后的Generator函数会返回一个Promise对象
co(f).then(()=>{
console.log('co执行完毕');
})