简介:Generator函数主要应用于函数的异步操作,在ES6诞生之前,异步编程的方法,大概有下面四种: “ 回调函数 、事件监听 、发布/订阅 、Promise对象 ”。但是Generator函数的出现使异步编程更加的方便快捷。
一、Generator函数应用研究
1. 测试小体验:
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next(); // {value: 3, done: false}
g.throw('出错了'); // 出错了 {value: undefined, done: true}
2. JS中的Thunk函数
简述:Thunk 函数是自动执行 Generator 函数的一种方法。编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
参考案例:
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
核心:javascript使传值调用的,并非传名调用,在javascript中,Thunk函数替换的而不是表达式,而是多参函数,将其替换成了一个只接受回调函数的作为参数的单参数函数。
测试案例分析:
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
return function (callback) {
return fs.readFile(fileName, callback);
};
};
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
* 且任何函数只要有回调参数,都可以写成Thunk函数形式,如下是一个简单的Thunk函数转换器
测试案例分析:
// ES5
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};
// ES6
const Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
3. 使用Thunk封装Generator函数
(1)测试案例:
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var gen = function* (){
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};
var g = gen();
var r1 = g.next();
r1.value(function (err, data) {
if (err) throw err;
var r2 = g.next(data);
r2.value(function (err, data) {
if (err) throw err;
g.next(data);
});
});
上面代码中,变量g是 Generator 函数的内部指针,表示目前执行到哪一步。next方法负责将指针移动到下一步,并返回该步的信息(value属性和done属性)。
仔细查看上面的代码,可以发现 Generator 函数的执行过程,其实是将同一个回调函数,反复传入next方法的value属性。这使得我们可以用递归来自动完成这个过程。
(2)Thunk 自动执行 Generator 函数
测试案例:
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
function* g() {
// ...
}
run(g);
4. 使用co模块自动执行Generator函数
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
var co = require('co');
co(gen);
且co函数返回一个Promise对象,因此可以用then方法添加回调函数:
co(gen).then(function (){
console.log('Generator 函数执行完成');
});
5. 使用Promise自动执行Generator函数
测试案例分析:
// 业务逻辑代码
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// 自动执行Generator函数封装
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
// 调用自动执行
run(gen);
6. Genarator函数使用示例
可以参考文章:https://blog.csdn.net/ganyingxie123456/article/details/78152770
二、async函数
为什么和Generator函数一起研究呢?因为async核心代码就是Generator实现的,可以说成async函数就是Generator得语法糖。
* async函数相对于Generator函数改进的地方:
1.自带执行器,类似于co + generator的结合
2.co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值;
3.返回值是 Promise;async函数内部return语句返回的值,会成为then方法回调函数的参数。
测试案例:指定多少毫秒后输出一个值
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50); // 指定 50 毫秒以后,输出hello world
* 使用注意点如下:
1.await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中
2.多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发,如下:
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
3.await命令只能用在async函数之中,如果用在普通函数,就会报错
4.async 函数可以保留运行堆栈