比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。 连续的执行就叫做同步。
一个有趣的问题是,为什么 Node 约定,回调函数的第一个参数,必须是错误对象err(如果没有错误,该参数就是null)?原因是执行分成两段,第一段执行完以后,任务所在的上下文环境就已经结束了。在这以后抛出的错误,原来的上下文环境已经无法捕捉,只能当作参数,传入第二段。
回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。
依次读取两个以上的文件的情况,就会出现多重嵌套。
Promise 是解决回调函数嵌套的一种方法,链式.then((res)=>return ‘这次结果’+res).then((res)=>{// res就是上次一的结果,再次return会进入下一then}) , 但是这种方法的缺点就是耦合性太高了,代码冗余,一堆then
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
var [x,y] = [1,2]
function foo(a) {
return a * 10 // (x+y)*10 把表达式传进来然后再计算结果,叫做【传名策略】
}
foo(x + y) // x+y= 3, 先计算结果,然后把结果传递进去,叫做【传值策略】
举例 :
function foo(a) {
let count = 0;
for (let i = 0; i < 10000; i++) {
count += a * i
}
return count;
}
foo(1 + 2)
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
// 正常版本的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);
const Thunk = function(fn) {
return function (...args) {
return function (callback) {
return fn.call(this, ...args, callback);
}
};
};
var readFile = Thunk(qs.readFile)
readFile(fileName)(callback)
function f(a, cb) {
cb(a);
}
const ft = Thunk(f);
ft(1)(console.log) // 1
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);
例子
// 一个普通的异步函数,接受3个参数,分别是延迟时间,执行处理函数,和回调函数
function delay(time, dealCallback, callback) {
setTimeout(function () {
dealCallback()
callback()
}, time ? time : 1000)
}
// 改造 Thunk 函数
function Thunk(time, dealCallback) {
return function (callback) {
delay(time, dealCallback, callback)
}
}
// 一个Generator函数,目的是连续发起2次异步请求
function *g() {
yield Thunk(null, function () {
console.log('first')
})
yield Thunk(500, function () {
console.log('second')
})
}
// 一个用于自动执行(管理)Generator函数的Thunk函数
// Generator函数的每个yield表达式,都是Thunk函数(不然next没有办法作为参数传过去)
function thunkForGenerator(callback) {
let g = callback()
function next() {
let result = g.next()
if (result.done) {
return
}
result.value(next) // result.value() 就是执行Thunk函数, 然后把next当做回调函数传*g, g函数会触发一次1000ms延迟的异步(thunkForAsync函数),等异步执行完毕后,g函数执行了thunkForGenerator函数的next方法,将控制权重新还给了他;
}
// 这里启动函数
next()
}
// 执行管理函数,将Generator函数作为参数传给他即可
thunkForGenerator(g)
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);
Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
两种方法可以做到这一点。
回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。
co模块github
参考文献: 阮一峰es6