在ES6中,主要涉及到的异步有Promise对象,Generator函数和async函数。我主要会从各个的定义、特点以及涉及到的一些方法、原理等进行梳理。
Promise对象
定义:Promise是一个对象,里面保存着未来可能发生的事件。值得注意的是,定义Promise对象里的函数会立即执行。
let promise = new Promise(function(resolve, reject){
if(异步成功){
resolve(value)
}
else{
reject(error)
}
}
)
在上述例子中,在定义promise对象的过程中,if-else语句会理解执行。
特点:Promise对象有三种状态,分别是:pending, resolved, rejected;
状态只能由异步操作改变;
状态一旦发生改变就不会再发生变化。
创建Promise对象接收一个函数作为参数,这个函数接收两个方法作为参数,一个为resolve,调用时表示事件的状态由pending变为resolved。另一个是reject函数,调用时表示对象的状态由pending转换为rejected。两个方法都可以传递参数。
实例方法:
1. then( )
接收两个函数作为参数,一个是resolve( )后执行的回调函数;一个是reject( )后执行的回调函数(可选),两个函数的参数都是定义Promise对象时resolve('参数')及reject函数传过来的。
返回的还是一个Promise对象,意味着Promise.resolve( ).then( ).then( )....then( )函数后面还可以再跟then()函数。
上个回调函数的返回值(return)可以在下个then( )里的回调函数参数中拿到。
2. catch( )
接收一个函数作为参数,主要处理catch( )方法之前异步过程的错误。回调函数的参数一般为error。
原理上,Promise.catch( function(err) { } ) 等价于 Promise.then( null, function(err){ } )。
reject('error')实际上等价于throw new Error( ),抛出一个错误。
Promise.then( ).then( ).catch( ),catch能捕获前面所有的错误。
3. finally( )
不管promise最后的状态,在执行完then或者catch指定的回调函数以后,都会执行finally方法指定的回调函数。
finally方法的回调函数不接受任何参数。
实现原理:
Promise.prototype.finally = function(callback){
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => {throw reason})
)
}
实例方法:
1. Promise.all( )
将多个promise实例,包装成一个新的promise实例;接受一个promise数组作为参数(const promise = Promise.all( [ p1, p2, ... ] ));当所有实例都变为resolved时,状态为resolved;当其中一个实例为rejected时,状态为rejected。
实现原理:
Promise.all = function(promises){
return new Promise(function(resolve, reject){
if(!isArray(promises)){
return reject(new Error('arguments must be an array'));
}
let resolvedNum = 0;
let length = promise.length;
let resolvedValue = new Array(length);
for(let i = 0; i < length; i++){ //使用let构建独立作用域,确保每个i是正确的值
Promise.resolve(promises[i]).then(function(value){ //给每个实例建立resolve和reject方法的监听
resolvedNum ++;
resolvedValue[i] = value;
if(resolvedNum === length){
resolve(resolvedValue);
}
},function(reason){
reject(reason);
})
}
})
}
2. Promise.race( )
与Promise.all类似,只要P1, P2, P3....之中有一个实例改变状态,则状态改变,率先返回的promise实例的值就传给回调函数。
实现原理和Promise.all( )类似。
3. Promise.resolve( )
将现有对象转为promise对象。
如果参数是一个thenable对象(thenable对象指的是具有then方法的对象),将这个对象转为Promise对象,立即执行then方法,状态转为resolved。
如果参数是一个promise对象,原封不动地返回这个实例。
如果参数不是具有then方法的对象,或者根本不是对象,则是如下情况:
let P = Promise.resolve('Hello');
P.then(function(s){
console.log(s); //'Hello'
})
如果不带任何参数,直接返回一个resolved状态的Promise对象。
Generator函数
语法上,Generator函数是一个状态机,封装多个内部状态;形式上,function关键字与函数名之间有一个星号( * ),函数体内部使用yield表达式,定义不同的状态。
Generator函数有几个特点:
1. 函数运行后,并不会立即执行,而是返回一个遍历器接口。用next( )函数执行代码。
2. yield(暂停标志): 用next方法执行代码时,遇到yield则暂停,并将后面的表达式的值作为返回对象的value值;如果没有遇到新的yield表达式,则执行到return语句,并将return后的表达式作为返回对象的value值;如果没有return语句,则返回undefined。
3. next()方法:可以向函数内部传值,next( )的参数作为上一个yield表达式的返回值。这样,Generator函数的妙处就在于你可以通过yield返回的对象取得一系列的值(这个也是‘generator’的含义),还可以通过next向内部传值。因此你可以从函数内部取值,也可以向函数内部传值。
4. yield*:用来在一个Generator函数里执行另一个Generator函数。
5. 应用场景:异步操作的同步化表达;控制流管理;部署Iterator接口(如 Object, 这样还可以使用for of)。
Generator函数 + yield语句可以实现异步应用。但是如何判断异步完成,将控制权再交还到Generator函数,需要有一个机制。
基于promise对象的自动执行:将异步操作包装成promise对象,用then方法交回执行权。因此yield语句后只能是一个promise对象。
例如:读取文件:
var fs = require('fs');
var readFile = function(fileName){
return new Promise (function(resolve, reject){ //将异步操作包装成一个promise对象
fs.read(fileName, function(err, data){
if(err){
return reject(err);
}
resolve(data);
})
})
}
var gen = function* (){
var f1 = yield readFile('./1.txt'); //保证yield语句后面是一个promise对象
var f2 = yield readFile('./2.txt');
console.log(f1.toString());
console.log(f2.toString());
}
//根据异步操作的结果自动执行gen函数
var g = gen();
g.netx().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); //data的值最后会返回给gen函数里的f1和f2变量
})
}
next();
}
run(gen);
async函数
本质上是Generator函数的语法糖。但是两者也存在区别:
1.内置执行器:async函数自带执行器,不需要像Generator函数一样需要next()函数或者机制。
2.更好的语义:async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
3.更广的适用性:await命令后面可以是Promise对象和原始类型的值。
4.返回的值是Promise对象:async函数的返回值是Promise对象,可以用then方法指定下一步操作。
基本用法:当函数执行的时候,遇到await就会先返回,等到异步操作完成,再接着执行函数体后面的语句。
返回的Promise对象状态:必须等到内部所有的await命令后面的Promise对象执行完,才会发生状态改变。但只要一个出错,函数就会终止执行,在此之后的代码都不会执行,可以将代码代入try catch中或者在await后面加上一个catch函数。
async function f(){
try{
await Promise.reject('error'); //将await语句放入try代码块中,避免发生错误后影响后续代码的执行
}catach(e){
}
return await Promise.resolve('Hello'); //可以继续执行
}
或者
async function f(){
await Promise.reject('error')
.catch(function(error){ //使用catch函数捕捉前面的错误
console.log(error);
})
return await Promise.resolve('Hello');
}
await命令:
1.一般后面跟的Promise对象,如果不是,会先转换为Promise对象。
2.最好放在try catach代码块中,防止出错导致终止。
3.多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时出发。
假设 getFoo和getBar是两个异步操作
let foo = await getFoo();
let bar = await getBar(); //这样会先执行getFoo,再执行getBar
改成同时触发:
let [foo, bar] = await Prmoise.all([getFoo(), getBar()]);
或者
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise; //只有在这里才可以拿到resolve里的值
let bar = await barPromise;
4.await只能在async函数中使用,在普通函数中使用会报错。
5.async函数的实现原理:将Generator函数和span函数包装在一个函数中。
async function fn(args){
// do something
}
等价于
function fn(args){
return span(function *(){
// do something
})
}
span函数的实现:
function span(genF){
return new Promise(function(resolve, reject){ //async函数返回的是一个promise对象
const gen = genF(); //剩下的跟前面的run函数类似
function next(data){
let result;
try{
result = gen.next(data);
if(result.done){
return resolve(result.value);
}
}catch(e){
reject(e);
}
Promise.resolve(result.value).then(function(data){ //result.value可能不是Promise对象,将其转换为Promise对象
next(data);
},function(err){
gen.throw(err);
})
}
next();
})
}
- 与其它异步处理方法的比较
假设某个DOM元素上面部署了一系列的动画,前一个动画结束,下一个动画才能开始。如果动画中有个出错,就不再往下执行,返回上一个成功执行的动画的返回值。
下面使用Promise对象、Generator函数和async函数三种方式来实现:
Promise:
function chainAnimations(elem, animations){
let ret = null;
let p = Promise.resolve(); //构建一个Promise对象
for(let anim of animations){
p = p.then(function(data){ //构建链式操作
ret = data;
return anim(elem); //执行异步操作
})
}
return p.catch(function(e){
}).then(function(){
return ret;
})
}
Generator:
function *chainAnimation(elem, animations){
return span(function *(){
let ret = null;
try{
for(let anim of animations){
ret = yield anim(elem); //执行异步操作,结果保存在ret中
}
}catch(e){
}
return ret;
})
}
async:
async function chainAnimations(elem, animations){
let ret = null;
try{
for(let anim of animations){
ret = await anim(elem);
}
}catch(e){
}
return ret;
}
从以上可以看出来它们之间的区别是:
- 因为Promise是链式的,所以有自己的catch函数。
- Generator函数必须使用执行器span,用户的代码写在span的参数中。
- async函数不需要执行器,并且更加同步化。
- Generator和async函数使用try catch函数更好,没有自己的catch函数。