ES6 Promise
一 Promise的含义
Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和强大。它由社区最早提出和实现,ES6将它写进了语言标准(原生提供),统一了用法。
所谓Promise,简单来说是一个容器,里面保存着某个未来才会结束的事件的结果。从语法上看,Promise是一个对象,它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise对象有如下两个特点:
- 1)对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已成功,又称为Fulfilled)和Rejected(已失败)。
只有异步操作的结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这一状态。这也是Promise名字的来由,即”承诺”,表示其他手段均无法改变。
- 2)一旦到达已完成(已成功和已失败)状态,状态将不再发生改变,且可以随时获取该状态的结果。
Promise对象状态的改变,只有两种可能:从Pending->Resolved状态和从Pending->Rejected状态。只要发生了这两种状态的改变,状态就凝固了,不会在改变,会一直保持这个结果。
这种状态发生改变后,再对Promise对象添加回调函数,也会立即得到该状态下的结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,就得不到结果了。
有了Promise对象,就可以将异步操作以同步操作的方式表达出来,避免了过多的回调嵌套。此外,Promise对象提供了统一的接口,使得异步操作更加容易控制。
当然,Promise也有一些缺点。首先,无法取消Promise操作,一旦建立了一个Promise,就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当Promise处于Pending状态时,无法得知目前该操作进展到哪一阶段(是刚开始?还是即将完成?)。
二 基本用法
在ES6中,Promise是一个构造函数,用来生成Promise实例。
var promise = new Promise(function(resolve,reject){
// some code
if(/* 异步操作成功 */){
resolve(value);
}else{
reject(error);
}
});
Promise构造函数接收一个函数作为参数(回调函数),该函数又会接收两个函数参数,即上面代码的resolve和reject。
resolve函数是在异步操作成功时调用(即resolved状态下),并将异步操作的结果作为参数传递给resolve函数。reject函数是在异步操作失败时调用(即rejected状态下),并将异步操作的错误信息作为参数传递给reject函数。
Promise实例生成以后,也可以用then方法来分别指定Resolved状态和Rejected状态的回调函数。
promise.then(function(value){
// success
},function(value){
// failure
});
then方法接收了两个回调函数作为参数,其中,前一个回调函数是Resolved状态下调用的,后一个回调函数是Rejected状态下被调用。第二个函数是可选的,就是说可以不传。这两个函数都可以接收Promise对象传出的的值作为参数。
下面是一个Promise对象的简单例子:
function timeOut(ms){
return new Promise((resolve,reject) => {
setTimeout(resolve,ms,'done');
});
}
timeOut(100).then(value => {
console.log(value);
});
注:上面”=>”表达式表示箭头函数,这也是ES6中的一个新增的特性。
上面代码中,timeOut返回一个Promise实例,表示一段时间以后会发生的结果。
一般情况下,Promise新建后就会立即执行。下面是一个用Promise异步加载图片的例子:
function loadImageAsync(url){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = function(){
resolve(image);
};
image.onerror new function(){
reject(new Error('Could not load image at'+url));
};
image.src = url;
});
}
下面是一个Promise对象实现的ajax操作的例子:
var getJSON = function(url){
var promise= new Promise(function(resolve,reject){
var client=new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept","application/json");
client.send();
function handler(){
if(this.readyState != 4) return;
if(this.state===200){resolve(this.response);}
else {reject(new Error(this.statusText));}
};
});
return promise;
};
getJSON("/post.json").then(function(json){
console.log("Contents: "+json);
},function(error){
console.log('出错了!',error);
});
三 Promise.protype.then()
Promise的then方法是定义在原型对象Promise.protype上,它的作用是为Promise实例添加状态改变时的回调函数。具体用法上一节中有了介绍。
then方法返回的是一个新的Promise实例(注:不是原来的那个Promise),因此可以采用链式写法。
getJSON("/post.json").then(function(json){
return json.post;
}).then(function(post){
// some code.
});
上面链式写法中,第一个回调函数完成后,会将返回结果作为参数,传入第二个回调函数。
采用链式写法的then函数,可以指定一组按照次序调用的回调函数。有可能,前一个回调函数返回的还是一个Promise对象(即有异步操作),那么后一个回调函数就会等待该Promise对象的状态发生变化,才会被调用。
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("Resolved: ", comments),
err => console.log("Rejected: ", err)
);
四 Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
getJSON("/posts.json").then(function(posts) {
// some code
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
上面代码中,getJSON方法返回一个Promise对象,如果该对象状态变为resolved,则调用then方法指定的回调函数;如果该对象状态变为rejected,则调用catch方法中指定的回调函数;另外,then方法中指定的回调函数,运行中抛出错误,也会被catch方法捕获。
这种写法比在then中设置rejected回调状态要好,原因是能捕获resolved状态回调函数中的错误,也更加接近同步的写法。
五 Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1,p2,p3]);
上面的代码中,Promise.all方法接受一个数组作为参数,p1,p2,p3都是Promise对象的实例(如果不是,会自动先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理)。
p的状态由p1,p2,p3决定,分成两种情况:
1)当三者状态均变成resolved时,p的状态才会变成resolved。此时p1,p2,p3的返回值组成一个数组传递给p的回调函数;
2)当p1,p2,p3其中之一的状态为rejected时,p的状态就会变成rejected。此时,第一个被rejected的实例的返回值,会传递给p的回调函数。
六 Promise.race()
Promise.race方法也是讲多个Promise实例,包装成一个新的Promise实例。
var p = Promise.race([p1,p2,p3]);
上面代码中,只要p1,p2,p3之中有一个实例率先改变状态,p的状态就跟着改变。而那个率先变化的Promise实例的返回值,就传递给p的回调函数。
七 Promise.resolve()
有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
var jsPromise = Promise.resolve($.ajax('../whatever.json'));
上面代码将jQuery生成的deferred对象,转为一个新的Promise对象。
Promise.resolve方法其实等价于下面的写法:
Promise.resolve('foo');
// 等价于
new Promise(resolve => resolve('foo'));
Promise.resolve方法的参数分为四种情况:
- 1)参数是一个Promise实例
Promise.resolve将不做任何修改,原封不动地返回这个实例。
- 2)参数是一个thenable对象
thenable对象指的是具有then方法的对象。
Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。
- 3)参数不是具有then方法的对象,或这根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为Resolved。
- 4)不带任何参数
Promise.resolve方法允许调用时不带参数,直接返回一个Resolved状态的Promise对象。
所以,如果希望得到一个Promise对象,比较方便的方法就是直接调用Promise.resolve方法。
八 Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致。
九 两个有用的附加方法
done()方法和finally()方法也是两个比较有用的方法。