Promise概述
1、异步处理的通用模型
ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled
和 settled
-
unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
-
settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转
异步操作总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。
异步操作分成了三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)
- pending:进行中,处于未决阶段,则表示这件事情还在进行(最终的结果还没出来)
- fulfilled:已成功,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
- rejected:已失败,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误
一旦这种状态改变,就会固定,不会再改变。是不可逆的Promise状态的改变只有两种情况:
- 1、从
pending
通过resolve()
变为fulfilled
。 - 2、从
pending
通过reject()
变为rejected
。
参考流程图:
2、Promise及其基本使用
为了解决地狱回调和异步操作之间的联系,ES6提出了一种异步编程大的解决方案Promise。但是Promise并没有消除回调,只是让回调变得可控。
Promise是一个对象,它可以获取异步操作的消息。为了方便和简易,下面的resolved
统指fulfilled
状态
const pro = new Promise((resolve, reject)=>{
/*
未决阶段的处理
通过调用resolve函数将Promise推向已决阶段的resolved状态
通过调用reject函数将Promise推向已决阶段的rejected状态
resolve和reject均可以传递最多一个参数,表示推向状态的数据
*/
if(true){
resolve()
}else{
reject()
}
})
pro.then(data=>{
/*
这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
data为状态数据
*/
},err=>{
/*
then函数也可以填catchable函数,也可以不填。我们最好是通过catch方法添加catchable函数
*/
})
pro.catch(err=>{
/*
这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
err为状态数据
*/
})
注意:
-
Promise创建后会立即执行。
-
thenable
和catchable
函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,加入的队列是微队列。 -
在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向
rejected
,并会被catchable
捕获。 -
一旦状态推向了已决阶段,无法再对状态做任何更改。
-
Promise并没有消除回调,只是让回调变得可控。
const pro = new Promise((resolve,reject)=>{
console.log("a")
setTimeout(() => {
console.log("d")
}, 0);
resolve(1)
console.log("b")
})
pro.then(data=>{
console.log(data)
})
console.log("c")
//a b c 1 d
以上代码,立即创建一个Promise函数,并且立即执行函数中的代码,所以首先输出a
,随后将setTimeout
中代码加入宏队列,然后通过resolve()
将Promise
的状态推向已决状态,但是resolve也是异步的,他会加入到微队列中等同步代码执行完毕后再执行。随后输出b
,Promise
函数执行完后,又执行剩下的同步代码输出c
,同步代码执行完毕后,执行微队列中的输出resolve
的结果值1
,再执行宏队列中的setTimeout
,输出b
。
3、Promise的方法then,catch,finally
(1)then()和catch()
then():注册一个后续处理函数,当Promise为resolved
状态时运行该函数
catch():注册一个后续处理函数,当Promise为rejected
状态时运行该函数
Promise对象中,无论是then
方法还是catch
方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:
- 如果当前的Promise是未决的,得到的新的Promise是进行中状态
- 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为
resolved
状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。
注意:后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态
如果前面的Promise的后续处理,返回的是一个Promise,则返回的新的Promise状态和后续处理返回的Promise状态保持一致。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
console.log(pro1)
// 异步调用,
const pro2 = pro1.then(result => result *2)
console.log(pro2)//pro2是一个Promise对象,状态是pending
pro2.then(result=>{console.log(result)},err=>console.log(err))
上述代码,在执行console.log(pro2)
的时候是同步执行, 此时pro1.then()
还未执行完毕,所以promise
还是pending
状态
const pro1 = new Promise((resolve,reject)=>{
throw 1
})
const pro2 = pro1.then(result => result *2,err=> err*3)
pro2.then(result=>{console.log(result*2)},err=>console.log(err*3))
//6
上述代码,对于串联的Promise,then和catch均返回一个全新的Promise,所以在pro1的catch执行时返回的pro2执行的是正常的,并非抛出错误。所以执行的为err * 3
,result * 2
。
const pro1 = new Promise((resolve,reject)=>{
throw 1
})
const pro2 = pro1.then(result => result *2,err=> err*3)
pro1.catch(err=>err*2)
pro2.then(result=>{console.log(result*2)},err=>console.log(err*3))
上述代码,每一次返回都是一个全新的Promise,所以pro1.catch(err=>err*2)
并没有变量接收。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
resolve(2)
})
const pro3 = pro1.then(result=>{
console.log(`结果${result}`) //1
return pro2
})
//pro3的状态是pending
pro3.then(result=>{
console.log(result)//2
})
上述代码,第一个then
方法指定的回调函数,返回的是另一个Promise
对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise
对象状态发生变化后再执行
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(2)
}, 3000);
})
pro1.then(result=>{
console.log(`结果${result}`) //1
return pro2
}).then(result=>{
console.log(result)//2
}).then(result=>{
console.log(result)//undefined
})
上面代码,最后一个输出undefined
是因为第二个then
方法没有返会值。
(2)finally()
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
pro1.then(result=>{
console.log(`结果${result}`) //结果1
return 3
}).then(result=>{
console.log(result)//3
}).catch(error=>{
console.log(error)
}).finally(()=>{
console.log("我一定会执行的")//我一定会执行的
})
4、Promise的静态成员
(1)resolve()
该方法返回一个resolved
状态的Promise,传递的数据作为状态数据。有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用。
const pro = Promise.resolve(1)
//等同于
const pro = new Promise((resolve,reject)=>{
resolve(1)
})
特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象
(2)reject()
该方法返回一个rejected
状态的Promise,传递的数据作为状态数据
const pro = Promise.reject(1)
//等同于
const pro = new Promise((resolve,reject)=>{
reject(1)
})
(3)all()
Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口。这个方法返回一个新的Promise对象,
返回的新的Promise对象的状态分成两种情况:
- 当参数中所有的Promise对象的状态都变成
fulfilled
,返回的Promise状态才会变成fulfilled
。此时参数的返回值组成一个数组,传递给新Promise的回调函数。 - 当参数之中有一个被
rejected
,新Promise的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给新Promise的回调函数。
//p1, p2, p3均是promise对象
const pro = Promise.all([p1, p2, p3]);
上述代码就是一个非常简单的调用方法
function getRandom(min,max){
return Math.random() * (max-min) + min
}
const proms = []
for(let i=0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(() => {
if(Math.random()<0.1){
reject(i)
}else{
resolve(i)
console.log(i,"完成")
}
}, getRandom(1000,5000))
}))
}
const pro = Promise.all(proms)
pro.then(res=>{
console.log("全部完成",res)
},err=>{
console.log(err,"错误")
})
上面代码是产生10个Promise对象,每个Promise对象都延迟时间1~5s中的随机时间后将状态推向已决,调用all方法,当10个Promise对象全部完成后再输出,或有一个错误的时候,也输出。
(4)race()
Promise.race()
方法同样是将多个 Promise 对象,包装成一个新的 Promise 对象。Promise.race()
方法的参数与Promise.all()
方法一样。
当参数里的任意一个Promise被成功或失败后,新Promise马上也会用参数中那个promise的成功返回值或失败详情作为参数调用新promise绑定的相应句柄,并返回该promise对象
function getRandom(min,max){
return Math.random() * (max-min) + min
}
const proms = []
for(let i=0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(() => {
if(Math.random()<0.1){
reject(i)
}else{
resolve(i)
console.log(i,"完成")
}
}, getRandom(1000,5000))
}))
}
const pro = Promise.race(proms)
pro.then(res=>{
console.log("有完成的",res)
},err=>{
console.log(err,"有错误")
})
console.log(proms)
上述代码和all()
例子一样,只不过,当有一个完成或失败的时候,就会执行pro
的then
或catch
回调函数
例子:异步加载图片
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve();
image.onerror = reject();
image.src = path;
});
};
一旦图片加载完成,就会Promise状态就会发生变化。
6、async 和 await
async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。实际上就是生成器函数的一个语法糖。目的是简化在函数的返回值中对Promise的创建。
(1)async
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
async function test(){
console.log(1);
return 2;//完成时状态数据
}
//等同于
function test(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
async function test(){
console.log(1)
return 2
}
const pro = test()
console.log(pro)//promise对象 promiseValue是2
注意:async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
(2)await
正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。await
类似于生成器的yield
,当遇到await
的时候,就会等待await
后面的Promise对象执行完毕后再继续执行下面代码。
async function test(){
const namePro = await getName();//异步ajax
const passwordPro = await getPassword();
}
test()
函数执行过程中,会先等待getName()
执行完毕后,再执行getPassword()
注意:await
关键字必须出现在async
函数中
如果多个await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
async function test(){
let namePro = getName();
let passwordPro = getPassword();
let name = await namePro;
let password = await passwordPro;
}
先让getName()
和getPassword()
执行,然后再等待结果
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable
中的状态数据。
async function test1(){
console.log(1);
return 2;
}
async function test2(){
const result = await test1();
console.log(result);
}
test2();
//等同于
function test1(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
function test2(){
return new Promise((resolve, reject)=>{
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
test2();
如果await
的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行
async function test(){
const result = await 1
console.log(result)
}
//---等同
function test(){
return new Promise((resolve,reject)=>{
Promise.resolve(1).then(data =>{
const result = data
console.log(result)
resolve()
})
})
}
最后附一个自己模仿封装的Promise
const MyPromise = (()=>{
const PENDING = "pending",
RESOLVE = "resolve",
REJECT = "reject",
PromiseValue = Symbol("PromiseValue"),
PromiseStatus = Symbol("PromiseStatus"),
changeStatus = Symbol("changeStaus"),
thenables = Symbol("thenables"),
catchables = Symbol("catchables"),
settleHandle = Symbol("settleHandle"),
linkPromise = Symbol("linkPromise");
return class{
//改变状态
[changeStatus](newStatus,newValue,queue){
if(this[PromiseStatus !== PENDING]) return
this[PromiseStatus] = newStatus
this[PromiseValue] = newValue
//执行相应队列中的函数
queue.forEach(handler => handler(newValue));
}
/*
executor未决阶段(pending状态)下的处理函数
*/
constructor(executor){
this[PromiseStatus] = PENDING;
this[PromiseStatus] = undefined;
this[thenables] = [] //resolved后续处理函数的数组
this[catchables] = []//rejected后续处理函数的数组
const resolve = data=>{
this[changeStatus](RESOLVE,data,this[thenables])
}
const reject = (reason)=>{
this[changeStatus](REJECT,reason,this[catchables])
}
try{
executor(resolve,reject)
}catch(err){
reject(err)
}
}
// 处理后续处理函数 后续处理函数,需要立即执行的状态,作业队列
[settleHandle](handler,isNow,queue){
if(typeof handler !== "function")return
if(this[PromiseStatus] === isNow){
setTimeout(() => {
handler(this[PromiseValue])
}, 0);
}else{
queue.push(handler)
}
}
[linkPromise](thenable,catchable){
return new MyPromise((resolve,reject)=>{
this[settleHandle](data=>{
try {
const result = thenable(data)//得到当前Promise
resolve(result)
} catch (error) {
reject(error)
}
},RESOLVE,this[thenables])
this[settleHandle](err=>{
try {
const result = catchable(err)
resolve(result)
} catch (error) {
reject(error)
}
},REJECT,this[catchables])
})
}
then(thenable,catchable){
return this[linkPromise](thenable,catchable)
}
catch(catchable){
return this[linkPromise](undefined,catchable)
}
static all(prms){
return new MyPromise((resolve,reject)=>{
})
}
static race(prms){}
static reject(reason){
if(reason instanceof MyPromise){
return reason
}else{
return new MyPromise(reject=>{
reject(reason)
})
}
}
static resolve(data){
if(data instanceof MyPromise){
return data
}else{
return new MyPromise(resolve=>{
resolve(data)
})
}
}
}
})()
const pro = new MyPromise((resolve,reject)=>{
console.log("未决阶段")
// throw new Error("111")
resolve(1)
})
pro.then(data=>{
console.log(data)
return 234
}).then(data=>{
console.log(data)
})
pro.catch(data=>{
console.log(data)
})
pro.catch(data=>{
console.log(data)
})
console.log(pro)