Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
Promise对象有以下两个特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
基本用法
使用一个构造函数来生成Promise实例,这里传进去的function立刻执行,甚至在Promise对象生成前就执行了:
var promise = new Promise(function(resolve, reject) {
someAsyncOperate(function(data,error){
if (!error){
//异步操作成功时,将结果传到resolve函数中
//这个函数是JS中已经定义好的,负责将promise的状态由Pending改为Resolved
//至于具体怎么处理data则会在后面具体配置
resolve(data);
} else {
//负责将promise的状态由Pending改为Rejected
reject(error);
}
});
});
我们来看一下promise完成前和完成后的状态:
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => resolve("P1 Result"), 1000)
})
console.log(p1);//Promise { <state>: "pending" }
setTimeout(() => console.log(p1), 3000)//Promise { <state>: "fulfilled", <value>: "P1 Result" }
配置具体的处理逻辑:
promise.then(function(value) {
//这个方法就是配置成功时怎么处理数据,value就是你传入resolve的参数
}, function(error) {
//失败时同理
});
有一点要注意,Promise新建后立即执行。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,如果有其他的异步操作,则是谁先完成谁先执行。
我们来看一个例子:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
});
}
//在这句话执行后200毫秒,异步操作就执行完了
//但是由于当前脚本中还有同步操作未执行
//这个回调函数很久以后才执行
timeout(200).then((value) => {
console.log(value);
});
//还有异步操作的情况
setTimeout(function(){
console.log("Other done!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}, 100);
//谁先完成谁先执行
//还有同步操作的情况
var i = 999999;
while (i>0) {
console.log("hahaha"+i);
i--;
}
//两个异步会在所有hahaha之后才输出
异步加载图片的例子:
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
var image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
loadImageAsync("http://car3.autoimg.cn/cardfs/product/g23/M03/AF/90/u_autohomecar__wKgFXFeGAuWAL38MAAXs-8pjxYg386.jpg").then(function(imageNode){
document.body.appendChild(imageNode);
});
一个异步操作的结果是返回另一个异步操作:
//P2和P1是两个异步操作,P2要依赖P1的结果
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => resolve("P1 Result"), 3000)
})
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => {
//将p1这个promise对象作为resolve的参数
//这时p1的状态就会传递给p2
//p1的状态决定了p2的状态
//如果p1的状态是Pending,那么p2的回调函数就会等待p1的状态改变
//如果p1的状态已经是Resolved或者Rejected,那么p2的回调函数将会立刻执行
resolve(p1);
console.log("P2 has done, waiting for P1")
}, 1000)
})
p2.then(result => {
console.log("P2 received P1:"+result)
})
//P2 has done, waiting for P1
//P2 received P1:P1 Result
Promise.prototype.then()
这个方法的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。这时第二个then方法的行为就和这个返回值有关了。
//P2和P1是两个异步操作,P2要依赖P1的结果
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => resolve("P1 Result"), 500)
});
p1
.then(function(value){
console.log(value);//P1 Result
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve("P2 Result:"+value), 1000)
})
//这里返回的是一个新的promise对象
//那么下面的then就会等待这个新的promise执行完成后再根据状态执行
//就相当于在这个返回的promise对象上调用then
return p2;
})
.then(function(value){
console.log(value);//P2 Result:P1 Result
//这里返回的是一个非promise类型
//那么这个返回值可以在下一个then中获取到
return "All Good";
})
.then(function(value){
console.log(value);//All Good
});
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。这个方法同样还会返回一个新的Promise对象
// 写法一
var promise = new Promise(function(resolve, reject) {
try {
throw new Error('test1');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
promise = new Promise(function(resolve, reject) {
reject(new Error('test2'));
});
promise.catch(function(error) {
console.log(error);
});
比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。
如果Promise状态已经变成Resolved,再抛出错误是无效的。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
虽然catch是then的简化写法,但是由于链式调用和catch的特性,把所有的错误用一个catch捕获是很方便的,所以一般使用catch。
promise
.then(function(data1) { //cb
// success
})
.then(function(data2) { //cb
// success
})
.catch(function(err) {
// error
});
如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。
Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
all的参数可以不是数组,但是需要具有Iterator接口;
p的状态由p1、p2、p3决定,分成两种情况:
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("P1 Result"), 1000);
})
var p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("P2 Result"), 500);
})
var p3 = new Promise((resolve, reject) => {
setTimeout(() => resolve("P3 Result"), 3000);
})
var all = Promise.all([p1, p2, p3]);
all
.then((results)=>{
console.log("All Done");
for (var i of results) {
console.log(i);
}
})
.catch(error => console.log(error));
Promise.race()
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
只要有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
这个很适合给一个或一组操作设置超时操作
var p1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("P1 Result"), 1000);
})
var p2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("P2 Result"), 500);
})
var timeout = new Promise((resolve, reject) => {
//setTimeout(() => reject("Time Out"), 3000);
setTimeout(() => reject("Time Out"), 3000);
})
var race = Promise.race([p1, p2, timeout]);
race
.then((result)=>{
console.log(result);
})
.catch(error => console.log(error));
//P2 Result
Promise.resolve()
有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。
参数分为4种情况:
参数是一个Promise实例
如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
参数是一个thenable对象
thenable对象指的是具有then方法的对象。
Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
参数不是具有then方法的对象,或根本就不是对象
Promise.resolve方法返回一个新的Promise对象,状态为Resolved。
var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
不带有任何参数
Promise.resolve方法允许调用时不带参数,直接返回一个Resolved状态的Promise对象。
需要注意的是,立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
Promise.reject()
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致。
两个有用的附加方法
done()
Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
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 })
);
};
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
Generator函数与Promise的结合
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
var g = function* () {
try {
var foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
var it = generator();
function go(result) {
if (result.done) return result.value;
//第一次执行next时返回的value是getFoo()返回的Promise对象
//通过then方法取到异步执行的结果
//使用next的参数将结果作为yield语句的返回值
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);