什么是Promise
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已拒绝)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
3. ES6 中的Promise
先在控制台打印出Promise看看,console.dir(Promise)
由上图可知,Promise是一个函数,自己身上有all、race、reject、resolve等方法,原型上有then、catch等方法
ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
创造了一个Promise实例:
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成');
var num = Math.random();
if(num>=0.5){
resolve(num);
} else{
reject('数字小于0.5');
}
}, 2000);
Promise新建后就会立即执行。
上面的代码中,设置了一个2s后执行的定时器。
2s以后输出“执行完成”,然后生成一个随机数,如果数字大于等于0.5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为“失败”的原因。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“已解决”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“拒绝”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
4. Promise的使用实例
下面是异步加载图片的例子。
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;
});
上面代码中,使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。
Promise.prototype.then()
then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
从表面上看,Promise只是简化了层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成1');
resolve('数据1');
}, 2000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
console.log('执行完成2');
resolve('数据2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
console.log('执行完成3');
resolve('数据3');
}, 2000);
});
return p;
}
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
以上代码的输出为:
执行完成1
数据1
执行完成2
数据2
执行完成3
数据3
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
var promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
上面代码中,promise抛出一个错误,就被catch方法指定的回调函数捕获。
需要注意的是,catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。
Promise.all()
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
下面是一个例子,其中 runAsync1、 runAsync1 和runAsync3上面的例子中已有定义
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
以上代码的输出为:
执行完成1
执行完成2
执行完成3
[“数据1”,” 数据2”,”数据3”]
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。
Promise.race()
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法race「谁跑的快,以谁为准执行回调」。
我们把上面runAsync1的延时改为1秒来看一下:
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
以上代码的输出为:
执行完成1
数据1
执行完成2
执行完成3
在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:
//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'xxxxxx';
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});
requestImg函数会异步请求一张图片,我把地址写为”xxxxxx”,所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。
vue 项目中实战:
三个异步promise方法:
async1(){
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('执行完成1')
resolve('数据1')
},1000)
})
return p1;
},
async2(){
let p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('执行完成2')
resolve('数据2')
},1000)
})
return p2;
},
async3(){
let p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('执行完成3')
resolve('数据3')
},1000)
})
return p3;
},
promise.then(catch) 方法:****【promise原型上的方法】
promiseThen(){
//promise.then 如果有一个then执行了reject,就会调用catch捕获错误,不会再调用其他then了
//为什么可以持续调用.then?因为每次都return 出去了想要调用的promise对象,就可以调用promise的原型上的then和catch方法了;
this.async1().then(res1=>{
console.log(res1)
return this.async2()
}).then(res2=>{
console.log(res2)
return this.async3()
}).then(res3=>{
console.log(res3)
}).catch(err=>{
console.log('调用then的时候,有一个报错了怎么办',err)
})
},
promise.all 方法:【promise身上的方法】
promiseAll(){
//promise.all 的时候,必须全部都执行resolve,才代表成功
Promise.all([this.async1(),this.async2(),this.async3()]).then(res=>{
console.log('执行了promise.all')
}).catch(err=>{
console.log('执行promise.all的时候有一个失败了')
})
},
promise.race 方法:****【promise身上的方法】
promiseRace(){
//promise.race 的时候,谁跑的快就以谁为准执行回调
Promise.race([this.async1(),this.async2(),this.async3()]).then(res=>{
console.log('执行了promise.race')
}).catch(err=>{
console.log('执行promise.race的时候,谁最先输出有一个失败了')
})
},