Promise的学习参考了以下几位大大们的博客~
https://www.liaoxuefeng.com/wiki/1022910821149312/1023024413276544
https://segmentfault.com/a/1190000007032448
http://es6.ruanyifeng.com/#docs/promise
1. 同步和异步
Promise是异步编程的一种解决方案,所以我们应该先来了解一下什么是同步和异步。
1.1 同步
同步的方法指的是方法一旦开始调用,调用者必须等到方法调用结束后,才可继续后续的行为。
console.log("开始")
while(1)
console.log("结束")
第二个打印语句是无法被打印出来的,因为这是一个同步模式,while死循环阻塞了进程。
1.2 异步
异步模式可以执行多个任务,如果任务A需要等待,可以先执行任务B,等到任务A结束后再继续进行函数的回调。
常见的异步模式有定时器、ajax请求。
console.log("开始")
setTimeout(function(){
console.log("定时器中的异步任务")
},0)//0毫秒后开始执行定时器内的回调函数
console.log("结束")
输出如下所示
我们可以看到,即使定时器的延迟时间为0。还是会先执行完同步的任务再执行定时器内的异步任务。即:异步任务会在当前脚本的所有同步任务执行完才会执行。
2. Promise
了解完同步和异步,我们就可以深入的了解一下Promise。
2.1 基本概念
1.Promise对象代表一个异步操作,具有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
2.Promise对象的状态改变,只能有两种可能:(1)pending–>fulfilled [称为resolved状态] (2)pending–>rejected [称为rejected状态]。
3.一旦状态发生了改变,状态就凝固了,会一直保持这个状态,不会再发生变化。
2.2 基本用法
在ES6中,Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise接受一个「函数」作为参数,该函数的两个参数分别是resolve和reject。这两个函数就是就是「回调函数」,由JavaScript引擎提供。
resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成后,可以使用then方法指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// resolved时调用
}, function(error) {
// rejected时调用
});
其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
let promise = new Promise(function(resolve, reject) {
console.log('Promise立即执行');
resolve();
});
promise.then(function() {
console.log('resolved打印输出');
});
console.log('同步打印输出');
可以看到,promise对象新建后立即执行。而在then方法中指定的回调函数,将在当前脚本所有同步任务执行完后才会进行。所以resolve打印输出在最后执行。
3. Promise对象的基本API
3.1 .then()
语法:Promise.prototype.then(onFulfilled, onRejected)
具体用法如上所述
3.2 .catch()
语法:Promise.prototype.catch(onRejected)
该方法是.then(undefined, onRejected)的别名,用于指定发生错误时的回调函数。
promise.then(function(data){
console.log('success');
}).catch(function(error){
console.log('error',error);
});
等同于
promise.then(function(data){
console.log('success');
}).then(undefined,function(error){
console.log('error',error);
});
(有需要添加的内容)
3.3 .all()
语法:Promise.all(iterable)
该方法用于将多个Promise实例,包装成一个新的Promise实例。
Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定。
当p1, p2, p3状态都变为fulfilled,p的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是 resolved的顺序)存入数组,传给p的回调函数
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve, 3000, "p1");
});
var p2 = new Promise(function(resolve,reject){
resolve("p2");
});
var p3 = new Promise((resolve,reject)=>{
setTimeout(resolve, 1000, "p3")
});
Promise.all([p1,p2,p3]).then(function(values){
console.log(values);
});
当p1, p2, p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个被reject的promise的返回值,传给p的回调函数
var p1 = new Promise(function(resolve,reject){
setTimeout(resolve, 1000, "p1");
});
var p2 = new Promise(function(resolve,reject){
setTimeout(reject, 2000, "p2");
});
var p3 = new Promise((resolve,reject)=>{
reject("p3")
});
Promise.all([p1,p2,p3]).then(function(values){
console.log("resolve",values);
},function(error){
console.log("reject",error);
});
可以看到 虽然p2,p3都被reject了,但第一个被reject的promise是p3,所以返回的是第一个被reject的promise的返回值。
3.3 .race()
语法:Promise.race(iterable)
Promise.race方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
var p1 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "p1");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "p2");
});
Promise.race([p1, p2]).then(function(value) {
console.log('resolve', value);
}, function(error) {
console.log('reject', error);
});
当数组中的promise对象race后,并不会取消其他promise对象的执行。
var fastPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('fastPromise');
resolve('resolve fastPromise');
}, 100);
});
var slowPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('slowPromise');
resolve('resolve slowPromise');
}, 1000);
});
// 第一个promise变为resolve后程序停止
Promise.race([fastPromise, slowPromise]).then(function (value) {
console.log(value); // => resolve fastPromise
});
3.4 .resolve()
语法:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
Promise.resolve('Success')
等同于
new Promise(function(resolve){
resolve('Success');
})
(1)当参数为promise实例时
不做任何修改,原封不动地返回这个实例。
(2)当参数为thenable对象(具有then方法的对象)
Promise.resolve方法会将这个对象转换为Promise对象,然后立即执行then方法;
let thenable = {
then:function(resolve,reject){
resolve("then console")
}
}
let p1 = Promise.resolve(thenable);
p1.then(function(value){
console.log(value);
})
使用了Promise.resolve()后,立即执行了then方法中定义的回调函数。
(3)当参数为不具有then方法的对象 或 不是对象
Promise.resolve方法返回一个新的Promise对象,状态为Resolved
var p1 = Promise.resolve('Success');
p1.then(function(value){
console.log(value);
})
通过Promise.resolve方法,p1成为了一个新的Promise实例,且状态为Resolved,所以会立即调用then中定义的回调函数。
3.5 .reject()
语法:Promise.reject(reason)
用法与Promise.resolve()类似,也是new Promise()的快捷方式,不过会让Promise对象立即进入rejected状态,立即执行then中指定的onRejected回调函数。
3.6 .finally()
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。
var p1 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "p1");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "p2");
});
Promise.race([p1, p2]).then(function(value) {
console.log('resolve', value);
}, function(error) {
console.log('reject', error);
}).finally(function(){
console.log("always Finally")
});
4. 为什么使用Promise
4.1 回调地狱
我们假设我们要知道小明的奶奶叫什么。而现在我们只能从小明的学校那(接口1)拿到小明老师的联系方式(成功data1),再从小明的老师那(接口2)拿到小明爸爸的联系方式(成功data2),最后从小明爸爸那(接口3)拿到了小明奶奶的名字(成功data3)。但其中也有出错的可能,比如小明爸爸忘了奶奶名字(失败error3),小明老师没有爸爸联系方式(失败error2),小明学校没有小明老师联系方式(失败error1)。
用原生的ajax来写上面的请求,是下面这样的
request('test1.html', '', function(data1) {
console.log('小明老师的联系方式:', data1);
request('test2.html', data1, function (data2) {
console.log('小明爸爸的联系方式:', data2);
request('test3.html', data2, function (data3) {
console.log('小明奶奶的名字为:', data3);
//request... 继续请求
}, function(error3) {
console.log('小明爸爸忘了奶奶名字:', error3);
});
}, function(error2) {
console.log('小明老师没有爸爸联系方式', error2);
});
}, function(error1) {
console.log('小明学校没有老师的联系方式:', error1);
});
我们可以看到,多层回调层层嵌套,让人晕头转向,这就是所谓的回调地狱。
4.2 Promise逃离回调地狱
我们来试一下用Promise来重写上例
sendRequest('test1.html', '').then(function(data1) {
console.log('小明老师的联系方式:', data1);
return sendRequest('test2.html', data1);
}).then(function(data2) {
console.log('小明爸爸的联系方式:', data2);
return sendRequest('test3.html', data2);
}).then(function(data3) {
console.log('小明奶奶的名字为:', data3);
}).catch(function(error) {
//用catch捕捉前面的错误
console.log('请求失败了, 失败原因为:', error);
});
5. Promise的注意点
5.1 reject和catch的区别
promise.then(onFulfilled, onRejected)
在onFulfilled中发生异常的话,在onRejected中是捕获不到这个异常的。
promise.then(onFulfilled).catch(onRejected)
.then中产生的异常能在.catch中捕获
一般情况,建议使用第二种,可以捕获之前所有的异常。
5.2 then中抛错必须在catch中捕获
如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误。
function taskA() {
console.log(x);
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
可以看到taskA抛出错误后,状态一直维持在reject的状态,taskB是不会执行的。直到catch了错误,才继续执行finalTask。
5.3 Promise的链式调用
每次调用then都会返回一个新创建的Promise对象,而then内部只是返回的数据。
(1)对同一个Promise对象同时调用then方法
var p1 = new Promise(function(resolve){
resolve(100);
});
p1.then(function(value){
return value * 2;
});
p1.then(function(value){
return value * 2;
});
p1.then(function(value){
console.log("finally" + value);
});
可以看到,then的调用是同时开始执行的,且传给每个then的value值都是100。
(2)使用链式调用then方法
var p2 = new Promise(function(resolve){
resolve(100);
})
p2.then(function(value){
return value*2;
}).then(function(value){
return value*2;
}).then(function(value){
console.log("finally"+value)
})
5.4 Promise状态的凝固
promise状态变为resolve或reject,就凝固了,不会再发生改变。