异步编程(promise/generator/async-await)
1. 传统异步编程
在传统异步编程中,通常采用回调函数的方式完成异步操作,比如setTimeout、setInterval、requestAnimationFrame等api,或者以下例子:
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);2. promise
定义
可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。
不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:
在 本轮 Javascript event loop(事件循环)运行完成 之前,then回调函数是不会被调用的。
通过
then()添加的回调函数总会被调用(状态不为pending时调用),即便它是在异步操作完成之后才被添加的函数。通过多次调用
then(),可以添加多个回调函数,它们会按照插入顺序一个接一个独立执行。
因此,Promise 最直接的好处就是链式调用(chaining),避免了回调地狱,异常捕获也可以“冒泡”。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});改写传统异步编程例子如下:
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);Api
[Promise.prototype.then()](http://es6.ruanyifeng.com/#docs/promise#Promise.prototype.then())
[Promise.prototype.catch()](http://es6.ruanyifeng.com/#docs/promise#Promise.prototype.catch())
[Promise.prototype.finally()](http://es6.ruanyifeng.com/#docs/promise#Promise.prototype.finally())
[Promise.all()](http://es6.ruanyifeng.com/#docs/promise#Promise.all()) 全为fulfilled或者出现第一个rejected后触发then回调。
[Promise.race()](http://es6.ruanyifeng.com/#docs/promise#Promise.race()) 只要其中有一个实例率先改变状态,整个的状态就跟着改变。
[Promise.resolve()](http://es6.ruanyifeng.com/#docs/promise#Promise.resolve())
[Promise.reject()](http://es6.ruanyifeng.com/#docs/promise#Promise.reject())
兼容性
总结
可以看到,Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
Promise 的最大问题是代码冗余,原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。
3. generator
定义
Generator 函数是一个状态机,封装了多个内部状态,通过执行 Generator 函数会返回一个遍历器对象。
Api
yield表达式:
暂停执行的标记;
通过yield* 可以嵌套Generator函数;
next():
只有调用next(),内部指针指向该语句时才会执行,等于手动“惰性求值“;
next方法调用时传参,该参数就会被当作上一个
yield表达式的返回值,可以在 Generator 函数开始运行之后,继续向函数体内部注入值; for...of可以自动遍历Generator 函数返回的遍历器对象,替代next();
return():
返回给定的值并结束生成器;
同样可以通过传参指定返回结果;
function* helloWorldGenerator() {//给function后添加*,声明一个Generator 函数
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()//next方法可以恢复执行
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }异步应用
虽然表示简洁了,但如果必须保证前一步执行完,才能执行后一步。光靠generator函数在流程管理上是不方便的(不太方便找到合适的时机执行的)。
通过以下两种方案可以做到:
(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。(Thunk模块)
(2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。 (co模块)
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);
});
});总结
相对于promise的代码冗余问题,generator函数的使用更为简洁。但是Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。
4. async-await
含义
async 函数就是 Generator 函数的语法糖。
Api
async:在function 前面带上async,用于声明async函数。
await:该指令会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果。如果不是 Promise 对象,就直接返回对应的值。
改进
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
(2)更好的语义。
(3)更广的适用性。
(4)返回值是 Promise。
兼容性
总结
async是揉合了promise和generator各自的优势,提炼而出的异步编程方式。
对比
sync 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。
补充
promise.resolve() 和 promise.reject() 的应用场景
创建promise对象的语法糖,用于将指定值或对象转化为promise对象。 Promise.resolve方法有下面三种形式:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(theanable);这三种形式都会产生一个新的Promise。其中:
第一种形式提供了自定义Promise的值的能力,它与Promise.reject(reason)对应。两者的不同,在于得到的Promise的状态不同。
第二种形式,提供了创建一个Promise的副本的能力。
第三种形式,是将一个类似Promise的对象转换成一个真正的Promise对象。它的一个重要作用是将一个其他实现的Promise对象封装成一个当前实现的Promise对象。例如你正在用bluebird,但是现在有一个Q的Promise,那么你可以通过此方法把Q的Promise变成一个bluebird的Promise。
实际上第二种形式可以归在第三种形式中。
generator的应用场景
可以生成一个无限列表,每次获取都递增1 (创建无限长的序列)
产生一个每次获取一个元素都有重要、高消耗的资源访问的列表,完成延迟加载模型来将高消耗的元素获取延迟到真正访问时,而不需要一开始就获取N次形成静态的数组(延迟执行)
管理异步流程
可以模拟长轮询实现
generator try...catch
Generator 函数内部有try...catch则在内部捕获内部的错误,若内部没有部署try...catch代码块,内部抛出的错误直接被外部catch代码块捕获。
如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行。
Last updated
Was this helpful?