ES6 原生提供了 Promise 对象。
Promise,就是一个对象,代表一个异步操作,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。
Promise构造函数,接收的参数是函数,两个(resolve、reject)分别代表:
▪ resolve:异步操作成功后的回调函数
▪ reject:异步操作失败后的回调函数
▪ 标准来讲,resolve是将Promise状态置为fullfiled,reject将Promise状态置为rejected
▪ Promise一般是包在一个函数中,在需要的时候去运行这个函数就可以,我们的Promise并不需要调用,外面一层的函数运行,Promise就会执行
Promise 对象有以下两个特点。
(1)状态不受外界影响。有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数(回调地狱)。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。也就是说then、catch、finally的回调都无法进入。
常用的:
Promise.prototype.then方法:链式操作
function runAsync(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成');
resolve('随便什么数据');
}, 2000);
});
return p;
}
runAsync().then(function(data){
console.log(data);
//后面可以用传过来的数据做些其他操作
//......
});
// 我们包装好的这个runAsync函数,会return出我们的Promise对象,也就是说,执行这个函数我们得到一个Promise对象,还记得Promise对象上有then,catch方法,这也是强大之处:
// 在runAsync()的返回上直接调用then方法,then接收到一个参数是函数,并且会拿到resolve中传的参数,运行这段代码,两秒后输出‘随便什么数据’。
// 也就是说then和我们的回调函数callback一个意思,在runAsync异步任务完成之后执行,这也就是Promise的作用了,简单来说,就是能把原来的回调写法分离开来,在异步操作执行完之后,用链式调用的方式执行回调函数。
对于上述的用法可能你会不屑一顾,觉得callbak传进去也可以实现
function runAsync(callback){
setTimeout(function(){
console.log('执行完成');
callback('随便什么数据');
}, 2000);
}
runAsync(function(data){
console.log(data);
});
效果与then一样,费劲写Promise干嘛,那么问题来了,如果是多层回调怎么办,如果callback也是一个异步操作,而且结束后也需要相应的回调函数,该怎么办,总不能定义callback2、callback3、···吧,而我们的Promise的优势在于,可以再then中继续回调Promise对象的返回,然后继续then进行回调操作。
链式操作的用法,从表面上看,promise只是能够简化层层回调的写法,而实质上,Promise精髓是“状态”,用维护状态、传递状态的方式使得回调函数能够及时调用,他比传递的callback函数简单、灵活得多,所以使用Promise的正确场景是这样的。
function runAsync1(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('异步任务1执行完成');
resolve('随便什么数据1');
}, 1000);
});
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;
}
// 调用1
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. 异步任务1执行完成
2. 随便什么数据1
3. 异步任务2执行完成
4. 随便什么数据2
5. 异步任务3执行完成
6. 随便什么数据3
// 调用2
runAsync1().then(function(data){
console.log(data);
return runAsync2();
}).then(function(data){
console.log(data);
return '直接返回数据'; //这里直接返回数据
}).then(function(data){
console.log(data);
});
// 输出结果2
1. 异步任务1执行完成
2. 随便什么数据1
3. 异步任务2执行完成
4. 随便什么数据2
5. 直接返回数据
Promise.prototype.catch方法:捕捉错误
前面的例子只有成功的的回调,没有失败的情况,reject就是讲Promise置为rejected状态,这样我们在then中就可以铺捉到,然后执行失败的回调。
除了和then第二个参数里面一样外,还有另外一个作用,在执行resolve的回调时,如果抛出异常,不会报错卡死js(js报错的话,后面代码不再执行,停止运行),而是执行catch方法
function getNumber(){
var p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
var num = Math.ceil(Math.random()*10);
if(num<=5){
resolve(num);
}else{
reject('数字太大了');
}
}, 2000);
});
return p;
}
// 链式catch 第一种用法
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
});
// 第二种用法,在then中第二个参数中回调
getNumber().then(
function(data){
console.log('resolved');
console.log(data);
},
function(reason){
console.log('rejected');
console.log(reason);
}
);
// 第三种,在执行resolve的回调时,如果抛出异常,不会报错卡死js(js报错的话,后面代码不再执行,停止运行),而是执行catch方法
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
console.log(somedata);
//此处的somedata未定义,报错、,走catch
})
.catch(function(reason){
console.log('rejected');
console.log(reason);
})
getNumber 函数用来异步获取数字,2s执行完成,如果数字小于5,认为成功,否则失败,getNumber默认向then传了两个参数,一个是resolve回调,另一个对应reject回调。
Promise.all方法
Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,代码如下:
这样子是三个异步操作并行执行,等到他们都执行结束才回到then中,all会把异步操作结果,也就是resolve的参数放到一个数组内传给then。
// 执行
Promise.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
//输出
1. 异步任务1执行完成
2. 异步任务2执行完成
3. 异步任务3执行完成
4. [‘随便什么数据1’,’随便什么数据2’,略不想写] all里面回调then中的输出
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据
Promise.race方法
rece用法,all方法效果实际上是[谁跑得慢,以谁为准执行回调],也就是说all是在所有的异步函数全部执行结束才走then函数,那么相对于另一个方法[谁跑得快,以谁为准执行回调],这就是rect,这个词就是赛跑的意思。
Promise.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
console.log(results);
});
// 这三个异步操作同样是并行执行的。结果你应该可以猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了。结果是这样的:
1. 异步任务1执行完成
2. 随便什么数据1 // race 中输入
3. 异步任务2执行完成 //rece虽然输出了,但是异步回调还在继续
4. 异步任务3执行完成
// 这个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,报出“图片请求超时”的信息。运行结果如下:
1. GET file://••••••••••••• 图片加载失败的报错
2. 图片请求超时