什么是异步
所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
ES5的异步编程方法
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
回调函数
- 含义
所谓回调函数(callback),就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。 - 代码示例
readflie(/etc,function(err,data){
if (err) throw err;
console.log(data);
})
- 回调函数的缺点:
当回调函数比较多时,容易出现回调函数的嵌套。(回调函数噩梦)
Promise 对象
- promise含义
它不是新的语法功能,而是一种新的写法 - 代码示例
var readFile = require('fs-readfile-promise');
readFile(fileA)
.then(function(data){
console.log(data.toString());
})
.then(function(){
return readFile(fileB);
})
.then(function(data){
console.log(data.toString());
})
.catch(function(err) {
console.log(err);
});
- promise缺点:
代码冗余。
协程
- 协程含义
多个线程互相协作,完成异步任务。 - 协程代码
function asnycJob() {
// ...其他代码
var f = yield readFile(fileA);
// ...其他代码
}
//上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线。
- 协程优点:
代码的写法非常像同步操作,去掉yield后,和同步一样。
ES6的异步编程方法
Generator函数
- Generator函数含义:
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。它可以实现暂停执行和恢复执行,是实现异步的根本原因。 - 代码示例
function *gen(x){
var y = yield x + 1
return y
}
运行结果截图
捕获.PNG
- Generator函数不同于其他函数的地方
即执行它不会返回结果,返回的是指针对象。调用指针 a 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 1 为止。 - next方法的作用:
next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。当done为true时,表示没有下一个阶段。 - Generator函数的三大特点:
- 暂停执行和恢复执行
- 函数体内外的数据交换
- 错误处理机制。
- 具体代码和截图
- next 方法返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数,这是向 Generator 函数体内输入数据。
- Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
function *gen(x){
try{
var y = yield x + 1
return y
}catch(e){
console.log(e)
}
}
捕获.PNG
- 虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)
Thunk 函数
- Thunk 函数含义
编译器的"传名调用"实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做 Thunk 函数。它是"传名调用"的一种实现策略,用来替换某个表达式。 - 代码示例
function f(m){
return m * 5
}
f(x + 4)
//thunk函数
var thunk = function(){
return x + 4
}
function f(thunk){
return thunk() * 5
}
- JavaScript 语言的 Thunk 函数
JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。
readFile(ileName,callback);
var readFileThunk = Thunk(fileName)
readFileThunk(callback)
var Thunk = functin(fileName){
return function(callback){
return readFile(fileName, callback);
}
}
- Thunk 函数作用:
Thunk 函数现在可以用于 Generator 函数的自动流程管理。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();
}
run(gen);
co 函数库
- 什么是co函数库
co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
比如,有一个 Generator 函数,用于依次读取两个文件。
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co 函数库可以让你不用编写 Generator 函数的执行器。
var co = require('co');
co(gen);
上面代码中,Generator 函数只要传入 co 函数,就会自动执行。
- co 函数库的原理
Generator 函数就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。
两种方法可以做到这一点。
(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。
co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。
- co 函数库的源码
function co(gen){
var ctx = this
return new Promise(function(resolve,reject){
if(typeof gen === 'function') gen = gen.call(ctx)
if(!gen || typeof gen.next !== 'function') return resolve(gen)
onFulfilled()
function onFulfilled(res){
var ret
try{
ret = gen.next(res)
}catch(e){
retrun reject(e)
}
next(ret)
}
})
}
function next(ret) {
if(ret.done) return resolve(ret.value)
var value = toPromise.call(ctx,ret.value)
if(value && isPromise(value)) return value.then(onFulfilled,onRejected)
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
}
- 并发的异步操作
co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。
这时,要把并发的操作都放在数组或对象里面。
// 数组的写法
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);
async 函数
- async 函数含义
async 函数就是 Generator 函数的语法糖。
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
- async 函数的优点
- 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
var result = asyncReadFile();
- 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
- 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
- async 函数的实现
async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function fn(args){
// ...
}
// 等同于
function fn(args){
return spawn(function*() {
//spawn 函数为自动执行器 ...
});
}
- async 函数的用法
同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。 - 注意点
- await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
- await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
- 如果确实希望多个请求并发执行,可以使用 Promise.all 方法。
网友评论