本篇文章详细介绍了 Promise
忽略标题 @…@
本文章详细讲解Promise
回调地狱 再看
在之前讲解生成器函数的时候我们就见识过了回调地狱 我们这里在复习一下
回调地狱就是不停的嵌套回调函数 语法混乱 不易维护
function pow(num,callback){
callback(num*num);
}
pow(1,function(v){
console.log(v);
pow(2,function(v){
console.log(v);
pow(3,function(v){
console.log(v);
});
});
});
Promise 基本使用
Promise 也是 异步编程的一种解决方案 语法上是一个构造函数 用来封装异步操作 并获取成功和失败的结果
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Promiese是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理
Promise 是一个对象 在实例化时需要传递一个函数作为参数 在这个回调里面又有两个参数 分别是 resolve和reject 那么通过实例化后的Promise对象的then方法来调用触发 resolve 模块 catch方法触发 reject 模块
resolve 代表执行成功
reject 代表执行失败
// 成功时
const p = new Promise((resolved,reject)=>{
resolved(1);
});
p.then(value=>{
console.log(value); // 1
},reason=>{
console.log(reason);
});
// 失败时1
const p = new Promise((resolved,reject)=>{
reject(1);
});
p.then(value=>{
console.log(value);
},reason=>{
console.log(reason); // 1
});
// 失败时2
const p = new Promise((resolved,reject)=>{
reject(1);
});
p.then(value=>{
console.log(value);
});
p.catch(reason=>{
console.log(reason); // 1
});
Promise API
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
- Promise.all()
- Promise.race()
Promise.prototype.then()
then() 方法返回一个 Promise (en-US)。它最多需要有两个参数:Promise 的成功和失败情况的回调函数
const p = new Promise((resolved,reject)=>{
// resolved("成功");
reject('失败');
});
p.then((value)=>{
// 成功时执行此回调
console.log(value);
},(reason)=>{
// 失败时执行此回调
console.log(reason);
});
Promise.prototype.catch()
catch() 方法返回一个Promise (en-US),并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同。 (事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected))
const p = new Promise((resolved,reject)=>{
// resolved("成功");
reject('失败');
});
p.then(undefined,(reason)=>{
// 失败时执行此回调
console.log(reason);
});
p.catch((reason)=>{
// 失败时执行此回调
console.log(reason);
});
Promise.prototype.finally()
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在then()和catch()中各写一次的情况
类似 try catch 语句的 finally 语句
try{
}catch(error){
}finally{
console.log("我总是能执行");
}
Promise 的 finally
必须要等Promise成功或者失败时才可以执行
像如下代码 finally 是不会执行的
const p = new Promise((resolved,reject)=>{
});
p.then((value)=>{
},(reason)=>{
});
p.finally(()=>{
console.log("我总是能执行");
});
如下代码会执行 finally
const p = new Promise((resolved,reject)=>{
resolved("成功!finally会执行");
});
p.then((value)=>{
},(reason)=>{
});
p.finally(()=>{
console.log("我总是能执行");
});
Promise.resolve()
Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有"then" 方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成
const p = Promise.resolve(1);
p.then(v=>{
console.log(v); // 1
});
带有"then" 方法
const p = Promise.resolve(1).then(v=>{});
p.then(v=>{
console.log(v); // undefined
});
romise.reject()
Promise.reject()方法返回一个带有拒绝原因的Promise对象
const p = Promise.reject("太穷了!");
p.catch(reason=>{
console.log(reason); // 太穷了!
});
Promise.all()
Promise.all() 方法接收一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入,并且只返回一个Promise实例, 那个输入的所有promise的resolve回调的结果是一个数组。这个Promise的resolve回调执行是在所有输入的promise的resolve回调都结束,或者输入的iterable里没有promise了的时候。它的reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息
all 方法就是可以批量执行多个Promise 如果批量执行的Promise有一个失败 则整体结果为失败 如果成功 那么就可以获取到所有Promise成功的结果
const p = Promise.all([new Promise((resolved,reject)=>{
resolved("1");
}) , new Promise((resolved,reject)=>{
resolved("2");
}), new Promise((resolved,reject)=>{
resolved("3");
}) ]);
p.then(values=>{
console.log(values); // ["1", "2", "3"]
},(reason)=>{
console.log("reason:"+reason);
});
Promise.race()
race 方法就是可以批量执行多个Promise 只取得最先执行成功的Promise
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝
const p = Promise.race([new Promise((resolved,reject)=>{
setTimeout(function(){
resolved("1");
},0);
}) , new Promise((resolved,reject)=>{
resolved("2");
}), new Promise((resolved,reject)=>{
resolved("3");
}) ]);
p.then(value=>{
console.log(value); // 2
},(reason)=>{
console.log("reason:"+reason);
});
const p = Promise.race([new Promise((resolved,reject)=>{
setTimeout(function(){
resolved("1");
},0);
}) , new Promise((resolved,reject)=>{
resolved("2");
}), new Promise((resolved,reject)=>{
reject("3");
}) ]);
p.then(value=>{
console.log(value); // 2
},(reason)=>{
console.log("reason:"+reason);
});
const p = Promise.race([new Promise((resolved,reject)=>{
setTimeout(function(){
resolved("1");
},0);
}) , new Promise((resolved,reject)=>{
reject("2");
}), new Promise((resolved,reject)=>{
resolved("3");
}) ]);
p.then(value=>{
console.log(value); // reason:2
},(reason)=>{
console.log("reason:"+reason);
});
Promise 相关问题
Promise的执行流程
Promise 链式调用
我们的 then 方法其实返回的还是一个Promise对象 所以我们可以继续调用Promise的方法
const p = new Promise((resolved,reject)=>{
setTimeout(()=>{
reject("失败!");
},1000);
});
p.then(v=>{
console.log(v);
}).catch(reason=>{
console.log(reason);
});
const p = new Promise((resolved,reject)=>{
setTimeout(()=>{
resolved("成功!");
},1000);
});
p.then(v=>{
console.log(v); // 成功!
}).then(v=>{
console.log(v); // undefined
}).then(v=>{
console.log(v); // undefined
});
Promise的执行时机
Promise 默认在实例化后就会自动执行
const p = new Promise((resolved,reject)=>{
console.log("执行了......");
}); // 执行了
如果我们并不希望Promise一开始就执行的话 我们可以用一个函数将Promise封装起来
function Request(num){
return new Promise((resolved,reject)=>{
if(num !== 0){
resolved("成功!");
}else{
reject("失败!");
}
});
}
Request(1)
.then(value=>{
console.log(value); // 成功!
});
Promise的状态了解
对象的状态不受外界影响。Promise对象代表一个异步操作,
有三种状态:Pending(进行中),Fulfilled(已成功)和Rejected(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。着也是Promise这个名字的由来,它的英语的意思就是承诺,表示其他手段无法改变
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Fulfiled和从Pending变为Rejected.只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。这时就称为Resolved(已定型).如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果
这里的成功和失败其实我们之前一直都在用 这里主要是理解一下 Promise 默认的状态是Pending 我们可以输出Promise 查看
console.log(new Promise((resolved,reject)=>{})); // Promise {<pending>}
console.log(new Promise((resolved,reject)=>{resolved(1);})); // Promise {<fulfilled>: 1}
console.log(new Promise((resolved,reject)=>{reject(1);})); // Promise {<rejected>: 1}
console.log(new Promise((resolved,reject)=>{ resolved(1); reject(2); })); // Promise {<fulfilled>: 1}
链式调用返回值和状态问题
- return 1
- throw 1
- throw new Error()
- return Promise.resolved()
- return Promise.reject()
- return new Promise((resolved,reject)=>{})
我们的 Promise 是可以有多种类型的返回值的 对应不同的返回值 有着不同的处理
在起始的Promise中如果不修改对应的状态 then 方法或者 catch方法是不会执行的 而且我们返回了一个数字 但是我们的 p 还是一个 Promise 对象 我们这里的返回值是在执行器函数内部返回的 并不影响Promise实例的返回值
const p = new Promise((resolved,reject)=>{
return 123;
});
p.then(value=>{
console.log(value);
}).then(value=>{
console.log(value);
});
一旦修改了 状态为 成功 后面的 then 都会执行 而且每一个then返回的都是一个Promise对象 但是除了第一个then能够输出内容其他的都是undefined
这是因为 我们的then返回的是一个新的Promise对象 而且 这个状态也没修改为成功
所以这个value是undefined
const p = new Promise((resolved,reject)=>{
resolved("success");
});
p.then(value=>{
console.log(value);
}).then(value=>{
console.log(value);
}).then(value=>{
console.log(value);
});
如果我们设置了第一个Promise的状态为 reject 那么可以通过 catch 来捕获 第一个catch捕获后下面的catch就不在捕获第一个Promise的失败信息 其他的catch就可以捕获其调用catch函数的Promise对象的失败信息 如果我们只在最后写了一个catch那么最终的捕获会放到最终的这个catch回调中 即使上面经过很多层的then
我们也叫这种情况为 异常穿透
const p = new Promise((resolved,reject)=>{
reject("error!");
});
p.then(value=>{
console.log(value);
}).then(value=>{
console.log(value);
}).catch(reason=>{
console.log(reason,1);
}).then(value=>{
console.log(value);
}).catch(reason=>{
console.log(reason);
});
如果我们直接 return 一个基本值 那么就会直接作为当前返回的Promise对象的then方法的成功回调里的value属性参数的值
也就是说 使用 return 返回内容的话 默认的会将Promise的状态修改为 成功状态
如下代码就会对应输出所返回的内容
const p = new Promise((resolved,reject)=>{
resolved("errorasad!");
});
p.then(value=>{
console.log(value);
return 123;
}).then(value=>{
console.log(value);
return 456;
}).then(value=>{
console.log(value);
})
我们可以输出一下看状态
const p = new Promise((resolved,reject)=>{
resolved("success!");
});
console.log(p.then(value=>{
console.log(value);
return 123; // [[PromiseState]]: "fulfilled" [[PromiseResult]]: 123
}));
前面的知识了解完毕之后 剩下的就方便了
throw new Error 或者 throw ‘Error’ 都将修改Promise状态为失败
const p = new Promise((resolved,reject)=>{
resolved("success!");
});
p.then(value=>{
console.log(value);
throw new Error("Error");
}).catch(reason=>{
console.log(reason);
});
const p = new Promise((resolved,reject)=>{
resolved("success!");
});
p.then(value=>{
console.log(value);
throw "Error";
}).catch(reason=>{
console.log(reason);
});
const p = new Promise((resolved,reject)=>{
resolved("success!");
});
/*
* [[PromiseState]]: "rejected"
* [[PromiseResult]]: "Error"
*/
console.log(p.then(value=>{
console.log(value);
throw "Error";
}));
```js
返回 Promise.reslove() 直接返回一个成功状态的Promise并携带其成功的数据
const p = new Promise((resolved,reject)=>{
resolved(1);
});
p.then(value=>{
console.log(value);
return Promise.resolve(2);
}).then(value=>{
console.log(value);
return Promise.resolve(3);
}).then(value=>{
console.log(value);
});
返回 Promise.reject() 直接返回一个失败状态的Promise并携带其失败的数据
不能再在回 Promise.reject() 的 then 中 利用第二个回调处理捕获到失败信息 必须是新的Promise对象的catch方法才可以
```js
const p = new Promise((resolved,reject)=>{
resolved(1);
});
p.then(v=>{
console.log(v);
return Promise.reject("error01");
}).then(undefined,reason=>{
console.log(reason);
});
返回一个新的 Promise 对象
const p = new Promise((resolved,reject)=>{
resolved("success");
});
p.then(value=>{
console.log(value);
return new Promise((resolved,reject)=>{
resolved("success2");
});
}).then(value=>{
console.log(value);
});
利用Promise执行有顺序的异步请求任务
// 1. 请求用户购买的订单信息
// 2. 请求用户的订单中的商品信息
function RequestData(userID){
return new Promise((resolved,reject)=>{
setTimeout(()=>{
if(userID === 1){
console.log("拿到用户ID:"+userID);
console.log("开始根据用户ID请求订单数据......");
console.log("订单ID获取成功");
resolved(2);
}
},1000);
});
}
RequestData(1)
.then(orderId=>{
return new Promise((resolved,reject)=>{
setTimeout(()=>{
if(orderId === 2){
console.log("拿到订单D:"+orderId);
console.log("开始根据订单ID请求商品数据......");
console.log("商品ID获取成功");
resolved(3);
}
},1000);
});
})
.then(goodsId=>{
console.log("拿到商品ID:"+goodsId);
});
上面我们发现我们也是避开了回调函数的混乱写法 使用promise看起来更加类似同步代码的写法
但是 我们上面有个问题就是 我已经封装好了一个 RequestData 函数用来返回Promise 那么我们在下面的then方法中应该是继续返回当前这个封装好的函数 不需要在重新返回一个Promise了 换成我们封装好的函数 方便我们以后修改 所以需要对函数进行一次改造
这次改造我们将主要的业务放到封装好的函数中去 then方法只负责传递数据并执行异步操作
// 1. 请求用户购买的订单信息
// 2. 请求用户的订单中的商品信息
function RequestData(requestResult,setp){
return new Promise((resolved,reject)=>{
if(setp == 1){
setTimeout(()=>{
if(requestResult === 1){
console.log("拿到用户ID:"+requestResult);
console.log("开始根据用户ID请求订单数据......");
console.log("订单ID获取成功");
resolved(2);
}
},1000);
}else if(setp == 2){
setTimeout(()=>{
if(requestResult === 2){
console.log("拿到订单ID:"+requestResult);
console.log("开始根据订单ID请求商品数据......");
console.log("商品ID获取成功");
resolved(3);
}
},1000);
}
});
}
RequestData(1,1)
.then(orderId=>{
return RequestData(orderId,2);
}).then(goodsId=>{
console.log("拿到商品ID:"+goodsId);
});
但是还有一点不够完美 现在我们是有两个顺序任务 假设以后有多个任务呢? 比如5 6 个 我们是不是得每一次都要手动传入一个对应的步骤 还要记住这个步骤 其实我们可以省略手动传入步骤这个操作 如果你希望手动传入那么这里就可以略过了~
所以我们接着改造一下
如下代码 利用了闭包的特性来实现自动累加步骤 我们主需要关注内部业务的判断就可以了 在外部 我们调用时只获取需要的数据即可
// 1. 请求用户购买的订单信息
// 2. 请求用户的订单中的商品信息
function RequestData(){
let setp = 0;
return function(requestResult){
setp++;
return new Promise((resolved,reject)=>{
if(setp == 1){
setTimeout(()=>{
if(requestResult === 1){
console.log("拿到用户ID:"+requestResult);
console.log("开始根据用户ID请求订单数据......");
console.log("订单ID获取成功");
resolved(2);
}
},1000);
}else if(setp == 2){
setTimeout(()=>{
if(requestResult === 2){
console.log("拿到订单ID:"+requestResult);
console.log("开始根据订单ID请求商品数据......");
console.log("商品ID获取成功");
resolved(3);
}
},1000);
}else if(setp === 3){
setTimeout(()=>{
if(requestResult === 3){
console.log("拿到商品ID:"+requestResult);
console.log("开始根据商品ID查询商品的分类......");
console.log("商品的分获取成功");
resolved(4);
// 清空驻留在内存的变量
setp = null ;
}
},1000);
}
});
}
}
let request = RequestData();
request(1).then(orderId=>{
return request(orderId);
}).then(goodsId=>{
return request(goodsId);
}).then(category=>{
console.log("拿到商品分类信息:"+category);
});
Promise的优缺点
- 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
- Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
JS的队列模型
- 宏队列
- 事件回调
- 定时器回调
- ajax回调
- 微队列
- Promise 回调
异步操作中微任务队列优先执行于宏任务队列
如下是一到练习题 如果不看答案能作对 对于promise的执行时机掌握的是还不错啦
setTimeout(()=>{
console.log(5);
},0);
const p = new Promise((resolved,reject)=>{
setTimeout(()=>{
console.log(2);
},0);
resolved(4);
console.log(1);
}).then(value=>{
console.log(value);
});
console.log(3);
答案
// 最先执行的肯定是同步代码 在本例子中同步代码有 23 行 和 28行 所以 肯定先输出这两个 就是1 3
// 接着是异步代码 15 行 20行 25行 都是 JS中微任务队列优先于宏任务队列执行 所以 接着输出 4 Promise
// 的异步队列是微任务队列
// 定时器是宏任务队列 然后 输出 5 2 因为5更靠前
// 最后结果是 1 3 4 5 2
当然最后的例子也可以写三个方法分别返回对应promise