协程
多个线程互相协作,完成异步任务。运行流程大致如下
- 协程A开始执行
- 协程A执行到一半,进入暂停,执行权转移到协程B
- 一段时间后协程B交还执行权
- 协程A恢复执行
Generator 函数的数据交换和错误处理
Generator 函数可以暂停执行和恢复执行、函数体内外的数据交换和错误处理机制,这是它能封装异步任务的根本原因。
异步任务的封装
var fetch = require("node-fetch");
function* gen() {
var url = "https://api.github.com/users/github";
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value
.then(function (data) {
return data.json();
})
.then(function (data) {
g.next(data);
});
流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)
Thunk 函数
参数的求值策略
传值调用
C 语言
对参数求值的时候,实际上还没用到这个参数,有可能造成性能损失。
传名调用
Thunk 函数的含义
编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
JavaScript 语言的 Thunk 函数
JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,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);
}
};
};
Thunkify 模块
thunkify只允许回调函数执行一次
Generator 函数的流程管理
Thunk 函数现在可以用于 Generator 函数的自动流程管理
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);
});
});
Thunk 函数的自动流程管理
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);
co 模块
Generator 函数的自动执行工具
使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象
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(gen).then(function () {
console.log("Generator 函数执行完成");
});
co 模块的原理
- 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
- Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。
基于 Promise 对象的自动执行
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());
};
var g = gen();
g.next().value.then(function (data) {
g.next(data).value.then(function (data) {
g.next(data);
});
});
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);
处理并发的异步操作
// 数组的写法
co(function* () {
var res = yield [Promise.resolve(1), Promise.resolve(2)];
console.log(res);
}).catch(onerror);
// 对象的写法
co(function* () {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2),
};
console.log(res);
}).catch(onerror);
处理 Stream
const co = require("co");
const fs = require("fs");
const stream = fs.createReadStream("./les_miserables.txt");
let valjeanCount = 0;
co(function* () {
while (true) {
const res = yield Promise.race([
new Promise((resolve) => stream.once("data", resolve)),
new Promise((resolve) => stream.once("end", resolve)),
new Promise((resolve, reject) => stream.once("error", reject)),
]);
if (!res) {
break;
}
stream.removeAllListeners("data");
stream.removeAllListeners("end");
stream.removeAllListeners("error");
valjeanCount += (res.toString().match(/valjean/gi) || []).length;
}
console.log("count:", valjeanCount); // count: 1120
});