Promise
在ES6之前,我们解决异步函数的通用方式是传入一个回调函数,在ES6之后我们可以用Promise去解决异步函数,ES7还能使用async
Promise的状态变更是一个微任务
Promise特性
- Promise对象存在状态,状态只能在内部被修改,状态:
pendding(进行中)、fulfilled(成功)、rejected(失败) - Promise状态变更之后就不可改变,且,只能从
pendding变为fulfilled或者rejected,不能从fulfilled变成rejected - Promise会吃掉错误,在new Promise实例中出现错误,会打印错误,但是不会影响JS主进程的执行
Promise基本使用
new Promise((resolve,reject)=>{})的构造函数中有两个函数;
resolve():将promise状态更改为fulfilled,它的参数将会传给then的第一个参数函数reject():将promise状态更改为rejected,它的参数将会传给then的第二个参数函数- 当使用
resolve()或reject(),之后,函数仍旧会继续执行,需要我们使用return终止掉防止意外
例子
const fn = ()=> {
return new Promise((resolve,reject)=>{
console.log("fn执行。。。") // Promise 新建之后就会立即执行
setTimeout(()=>{
console.log("fn的回调执行。。。");
return resolve('1243') // 使用 return 终止掉 函数的执行
// console.log("继续执行") 不加return 这行就会继续执行
},1000)
})
};
console.log("主进程开始")
fn().then((res)=>{
console.log("promise状态已决",res)
})
console.log("主进程结束")
// 输出
// 主进程开始
// fn执行。。。
// 主进程结束
// fn的回调执行。。。
// promise状态已决 1243
Promise构造函数中的resolve()可以返回另一个Promise实例,此时,当前的Promise将由返回的那个Promise实例决定状态。
const p1 = ()=> {
return new Promise((resolve,reject)=>{
console.log("p1执行。。。") // 先建就立即执行
setTimeout(()=>{
console.log("p1的回调执行。。。");
resolve('1243')
},1000)
})
};
const p2 = ()=> {
return new Promise((resolve,reject)=>{
console.log("p2执行。。。") // 先建就立即执行
setTimeout(()=>{
console.log("p2的回调执行。。。");
resolve(p1()) // 这里的p2返回的是一个promise实例p1
},1000)
})
};
p2().then((res)=>{
console.log("promise状态已决",res)
})
/**
* 输出
p2执行。。。
p2的回调执行。。。
p1执行。。。
p1的回调执行。。。
promise状态已决 1243
*/
看上面的代码,我们可知,p2返回的是P1的promise实例,p2的状态由p1决定,当p1调用resolve(),p1的状态是fulfilled,那么p2的状态就是fulfilled,p1的状态是rejected,那么p2的状态就是rejected;p2的状态由p1的状态决定
Promise.resolve()
Promise.resolve(abc)
相当于
new Promise(resolve => resolve(abc))
- 这个函数的作用是:简化我们新建Promise对象的写法
- 这个函数是一个幂等方法(多少次调用都是一样的结果)
- 这个函数会把传入的参数,包装成一个Promise对象,修改Promise状态为
fulfilled,并返回
当传入的是一个thenable对象时,它会先执行thenable对象的then方法
thenable 对象:这个对象中有 then 方法
const foo = {
then(resolve,reject){
console.log("then方法执行。。。")
resolve(123);
}
}
Promise.resolve(foo).then(r=>{
console.log("Promise.resolve()的then方法开始执行。。",r)
})
/**
*
then方法执行。。。
Promise.resolve()的then方法开始执行。。 123
*/
如上所示,foo是一个thenable对象,且使用resolve()返回了一个值42,随后在Promise.resolve().then()中执行它
Promise.reject()
Promise.reject(abc)
相当于
new Promise((resolve,reject)=>reject(abc))
作用是立即创造一个状态为rejected的Promise对象,并返回
Promise.prototype.then()
我们在proimise实例上可以使用then(onFulfilled,onRejected)方法捕获promise的状态
then(...)是一个微任务
onFulfilled:可选; 此函数将捕获Promise状态为fulfilled,也就是在new Promise((resolve,reject)=>{})执行时,使用构造函数的第一个参数resolve更改的状态,它的参数就是resolve的参数。onRejected:可选;此函数将捕获Promise状态为rejected,也就是在new Promise((resolve,reject)=>{})执行时,使用构造函数的第二个参数reject更改的状态,它的参数就是reject的参数then返回一个新的Promise实例(如果不指定的话,那么就是返回undefined,相当于then(res=>Promise.resolve(undefined))),因此可用于链式调用- 如果
then()不传第二个参数的话,我们还可以通过.catch去捕获状态为rejected的promise,如果是链式调用的话,那么任一个promise实例出现rejected都会进入catch中,并中止链式调用执行 - 如果
onFulfilled或者onRejected不是一个函数,那么将会被忽略,且新的Promise的状态受之前的promise状态的影响
例子
const fn = new Promise((resolve,rej)=>{
return resolve(123)
})
console.log("主进程 S")
fn.then(res=>{
console.log("状态为:fulfilled",res)
});
console.log("主进程 E")
/**
主进程 S
主进程 E
状态为:fulfilled 123
*/
从上我们可以看到,then方法中的onFulfilled得到了resolve的返回值,因为此时的Promise状态是fulfilled
const fn = new Promise((resolve,reject)=>{
return reject(123)
})
console.log("主进程 S")
fn.then(res=>{
console.log("参数是:",res)
},(err)=>{
console.log("状态为:rejected",err)
});
console.log("主进程 E")
/**
主进程 S
主进程 E
状态为:rejected: 123
*/
从上我们可以看到,then方法中的onRejected得到了reject的返回值,因为此时的Promise状态是fulfilled,上面的例子,我们还可以使用.catch改造。
看下面的例子
const fn = new Promise((resolve,reject)=>{
return reject(123)
})
console.log("主进程 S")
fn.then(res=>{
console.log("参数是:",res)
}).catch((err)=>{
console.log("状态为:rejected",err)
})
console.log("主进程 E")
/**
主进程 S
主进程 E
参数是: 123
*/
我们使用.catch捕获了状态为rejected的Promise实例。
如果onFulfilled或者onRejected不是一个函数,那么将会被忽略,且新的Promise的状态受之前的promise状态的影响,让我们看下面的例子:
Promise.resolve(123).then("字符串A").then(res=>console.log("fulfilled:",res));
// 输出 fulfilled: 123
Promise.reject(123).then("字符串A","字符串B").then(res=>console.log("fulfilled:",res),err=>console.log("rejected:",err));
// 输出:rejected: 123
我们看到第一行代码,因为then传入的是一个字符串,所以被忽略了,这个then新建的Promise实例,受上一个Promise状态的影响。
链式调用
then方法返回的是一个Promise实例,所以能进行链式调用,让我们来看下面的例子:
const p1 = ()=> new Promise((resolve,reject)=>{
console.log('立即执行p1。。。');
setTimeout(()=>{
console.log('p1执行。。。');
resolve(123);
},1000)
})
const p2 = (v)=> new Promise((resolve,reject)=>{ // 假设 p2 是需要 p1 的参数才能去执行异步操作
console.log("立即执行p2。。。")
setTimeout(()=>{
console.log('p2执行。。。');
resolve(`${v}456`);
},1000)
})
// 链式调用
p1().then(resP1=>{
console.log("p1的状态为 fulfilled:",resP1)
return p2(resP1); // p2 继续执行。。
}).then(resP2=>{
console.log("p2的状态为 fulfilled:",resP2)
return 'pppp' // 这里返回一个 常量 这个常量生成的Promise对象状态默认就是 fulfilled
}).then(res=>{
console.log("这个promise的状态为 fulfilled",res)
})
/**
立即执行p1。。。
p1执行。。。
p1的状态为 fulfilled: 123
立即执行p2。。。
p2执行。。。
p2的状态为 fulfilled: 123456
这个promise的状态为 fulfilled pppp
*/
我们通过then可返回一个Promise对象的特性,实现了链式调用,当异步操作p1执行完毕,在执行异步操作p2;p2.then中我们return了一个常量,这个常量生成的Promise的状态默认就是fulfilled。
当链式调用的任何一个环节发生错误,都将阻止链式调用,进入到 catch()中
const p1 = ()=> new Promise((resolve,reject)=>{
console.log('立即执行p1。。。');
setTimeout(()=>{
console.log('p1执行。。。');
resolve(123);
},1000)
})
const p2 = (v)=> new Promise((resolve,reject)=>{ // 假设 p2 是需要 p1 的参数才能去执行异步操作
console.log("立即执行p2。。。")
setTimeout(()=>{
console.log('p2执行。。。');
reject(`${v}456`); // 假设 p2 异步操作失败了
},1000)
})
// 链式调用
p1().then(resP1=>{
console.log("p1的状态为 fulfilled:",resP1)
return p2(resP1); // p2 继续执行。。
}).then(resP2=>{
console.log("p2的状态为 fulfilled:",resP2)
return 'pppp' // 这里返回一个 常量
}).then(res=>{
console.log("这个promise的状态为 fulfilled",res)
}).catch(err=>{
console.log("rejected:",err);
})
/**
立即执行p1。。。
p1执行。。。
p1的状态为 fulfilled: 123
立即执行p2。。。
p2执行。。。
rejected: 123456
*/
从上面例子可以看到,p2的状态是rejected,所以,链式调用失败,进入到了 catch 中。
Promise.prototype.catch()
提供Promise实例的错误捕获机制。
- Promise的状态为rejected 或者then中出现错误,都将走到catch中
- Promise.prototyp.catch 将返回一个新的Promise实例,默认是返回
Promise.resolve(undefined),状态为fulfilled - 其实它的本质就是
then的语法糖,内部实现大概就是.then(null,onRejected(err=>Promise.resolve(undefined))) - Promise.prototyp.catch 同 Promise.prototyp.then 一样是微任务 基本用法:
const p = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("异步操作完成,假设失败了");
return reject(123); // 假设失败了
})
})
p().then(res=>{}).catch(err=>{
console.log("rejected",err);
})
当链式调用时,任何一个Promise实例状态为rejected或者then中发生错误都将走到catch中
const p = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("异步操作完成");
return resolve(123);
})
})
p().then(res=>{
console.log("此时的promise状态为:fulfilled")
console.log("这里输出一个不存在的值",aaa); // then 内部这里抛出一个错误
}).catch(err=>{
console.log("rejected:",err);
})
// rejected: ReferenceError: aaa is not defined
可以看到,我们的Promise实例p的状态已经是fulfilled了,但是,由于在then中出现了错误,所以我们进入了catch中
const p1 = Promise.reject(234).catch(err=>err); // catch 默认返回一个 Promise实例,相当于 Promise.resolve(err)
p1.then(res=>{console.log("p1的状态为fulfilled :",res)})
// p1的状态为fulfilled : 234
const p2 = Promise.reject(123);
const p3 = p2.catch(err=>Promise.reject(456));
console.log(p3) // rejected
我们看到这里p1的状态是 fulfilled ,因为 catch 默认返回了一个fulfilled的状态
p2是一个rejected状态的Promise,在p2的catch中返回了一个Promise状态为rejected并赋值给P3,我们可以看到p3的状态为rejected
setTimeout(()=>console.log("宏任务setTimeout执行"),0)
console.log("主进程 S")
p1.catch(err=>{console.log("catch执行")});
console.log("主进程 E")
/*
主进程 S
主进程 E
catch执行
宏任务setTimeout执行
*/
从上可以看出catch的操作是异步的(很合理,毕竟是then的语法糖)
Promise.all()
假如我们的异步操作没有彼此之间的依赖关系,那么我们就不需要链式调用了,可以使用all()进行并发请求。
- Promise.all([])中传入一个Promise实例数组(不一定要数组,只要具有
Iterator接口的数据也行) - Promise.all([])只有,所有的Promise实例都成为
fulfilled才能进入then的onFulfilled中 - Promise.all([]),中的Promise实例,当自己定义了catch时,就不会被Promise.all()判断为
rejected
const p1 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p1完成");
return resolve(123);
},500)
})
const p2 = ()=>new Promise((resolve,reject)=>{ // 假设p2 的异步操作为 1S
setTimeout(()=>{
console.log("p2完成");
return resolve(456);
},1000)
})
const p3 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p3完成");
return resolve(789);
},0)
})
Promise.all([p1(),p2(),p3()]).then((res=>{ // 当使用Promise.all时,onFulfilled 的参数为 结果数组,顺序按照调用顺序排序
const [resP1,resP2,resP3] = res;
console.log("看看p1:",resP1)
console.log("看看p2:",resP2)
console.log("看看p3:",resP3)
}))
当使用Promise.all时,onFulfilled 的参数为 结果数组,顺序按照调用顺序排序 只有当p1,p2,p3的状态都为
fulfilled才会进入then方法,then(onFulfilled,onRejected)中的onFulfilled参数为一个结果数组,顺序是all中的调用顺序(不受异步操作时长的控制)。
看下面的例子,我们修改p2的状态为:rejected
const p1 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p1完成");
return resolve(123);
})
})
const p2 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p2完成");
return reject(456); // 假设失败了
},1000)
})
const p3 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p3完成");
return resolve(789);
})
})
Promise.all([p1(),p2(),p3()]).then((res=>{ // 当使用Promise.all时,onFulfilled 的参数为 结果数组,顺序按照调用顺序排序
const [resP1,resP2,resP3] = res;
console.log("看看p1:",resP1)
console.log("看看p2:",resP2)
console.log("看看p3:",resP3)
})).catch(err=>{
console.log("发生错误:",err)
})
// p1完成
// p3完成
// p2完成
// 发生错误: 456
当然了,我们使用then(onFulfilled,onRejected)中的onRejected也是一样的效果:
const p1 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p1完成");
return resolve(123);
})
})
const p2 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p2完成");
return reject(456); // 假设失败了
},1000)
})
const p3 = ()=>new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p3完成");
return resolve(789);
})
})
Promise.all([p1(),p2(),p3()]).then((res=>{ // 当使用Promise.all时,onFulfilled 的参数为 结果数组,顺序按照调用顺序排序
const [resP1,resP2,resP3] = res;
console.log("看看p1:",resP1)
console.log("看看p2:",resP2)
console.log("看看p3:",resP3)
}),err=>{
console.log("看看错误:",err);
})
/**
p1完成
p3完成
p2完成
看看错误: 456
*/
当我们在Promise实例中,返回了自身的.catch函数,Promise.all()将忽略掉它(也就是不会进入Promise.all的方法中)
上面这段话也很好理解,Promise.catch 实例返回的是一个新的Promise对象,且它的状态是
fulfilled,自然Promise.all会成功处理,进入onFulfilled中
const p1 = ()=> new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p1完成");
return resolve(123); // 假设失败了
})
})
const p2 = ()=> new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p2完成");
return reject(456); // 假设失败了
},1000)
})
.catch(err=>{
console.log("p2内部的catch执行了。。。",err)
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p3完成");
return resolve(789); // 假设失败了
})
})
Promise.all([p1,p2,p3]).then((res=>{ // 当使用Promise.all时,onFulfilled 的参数为 结果数组,顺序按照调用顺序排序
console.log("看看结果:",res);
}),err=>{
console.log("Promise.all的catch执行了:",err);
})
/**
p1完成
p3完成
p2完成
p2内部的catch执行了。。。 456
看看结果: [ 123, undefined, 789 ]
*/
可以看到,我们给p2定义了catch函数,当p2的状态为rejected时,Promise.all将继续执行
Promise.race()
与Promise.all()需要全部的Promise实例的状态都为fulfilled才执行then(onFulfilled,onRejected)不同。Promise.race()是只要其中的一个Promise实例状态发生改变就去执行then(onFulfilled,onRejected)
- 和Promise.all(),一样,传入一个Promise实例数组,或者具有
Iterator接口的数据也行 - 当这个Promise实例数组中的某一个实例状态发生改变,那就执行then
const p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p1完成");
return resolve(123); // 假设失败了
},500)
})
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p2完成");
return resolve(456); // 假设失败了
},1000)
})
.catch(err=>{
console.log("p2内部的catch执行了。。。",err)
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("p3完成");
return resolve(789); // 假设失败了
})
})
Promise.race([p1,p2,p3]).then((res=>{
console.log("看看结果:",res);
}),err=>{
console.log("Promise.all的catch执行了:",err);
})
/**
p1完成
看看结果: 123
p3完成
p2完成
*/
从输出我们可以看出来,p1、p2、p3的Promise实例数组,p3的异步操作最早完成,于是Promise.race的then执行了,且值是p3的值
重头戏来了,让我们根据PromiseA+规范实现一个简易版的Promise
Promise实现
参考:juejin.cn/post/699459…
PromiseA+规范:promisesaplus.com/
源码
/**
* promise的实现
*/
/**
* promise的实现
*/
class MyPromise{
constructor(handler){
this.PromiseResult = null // 终值
this.STATUS = ["PENGDING","FULFILLED","REJECTED"]
this.PromiseState = this.STATUS[0]; // 状态
// 添加执行队列
this.onFulfilledCallbacks = []; // 成功回调
this.onRejectedCallbacks = []; // 失败回调
this.initBind();
try {
handler(this.resolve,this.reject)
} catch (error) {
this.reject();
}
}
resolve(val){
if(this.PromiseState!=this.STATUS[0])
return
this.PromiseResult = val;
this.PromiseState = this.STATUS[1];
// 依次执行队列
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
reject(val){
if(this.PromiseState!=this.STATUS[0])
return
this.PromiseResult = val;
this.PromiseState = this.STATUS[2]
// 依次执行队列
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult)
}
}
// 两个回调
/**
* 这里就需要调用到then的特性了
* 1、链式调用: // 链式调用 输出 200
const p3 = new Promise((resolve, reject) => {
resolve(100)
}).then(res => 2 * res, err => console.log(err)).then(res => console.log(res), err => console.log(err))
2、下一个then的状态依赖于第一个then的onFulfilled、onRejected返回值,
若第一个then的回调函数返回的是一个Promise,那么当这个Promise为成功时下一个then执行onFulfilled;失败时,下一个then执行onRejected
若第一个then的回调函数返回的不是一个Promise,那么无论这个then是成功的还是失败的,下一个then都默认执行onFulfilled
* @param {*} onFulfilled
* @param {*} onRejected
* @returns
*/
then(onFulfilled,onRejected){
const isFunction = (f)=> Object.prototype.toString.call(f)==="[object Function]"
// 保证进来的是函数类型
// 即使不是,也可以使用适配器进行转换
onFulfilled = isFunction(onFulfilled) ? onFulfilled :val=>val;
onRejected = isFunction(onRejected) ? onRejected :reason=>{throw reason};
var thenPromise = new MyPromise((resolve,reject)=>{
const resolvePromise = cb =>{
// 这里加的setTimeOut是为了兼容 同步的promise(就是实现了一个微任务)
setTimeout(()=>{
try {
const x = cb(this.PromiseResult) // 获取return 的值
if(x===thenPromise){ // 如果这个return 返回时自身的话
throw new Error("不能返回自身")
}
if(x instanceof MyPromise){ // 如果这个return 返回的是 promise的话
x.then(resolve, reject)
}else{ // 不是的话,那就是 字符或者其他类型 此时是默认是成功状态的
resolve(x)
}
} catch (error) {
reject(err)
}
})
}
if(this.PromiseState===this.STATUS[1]){
resolvePromise(onFulfilled)
// onFulfilled(this.PromiseResult);
}
if(this.PromiseState===this.STATUS[0]){
this.onFulfilledCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
if(this.PromiseState===this.STATUS[2]){
resolvePromise(onRejected)
// onRejected(this.PromiseResult);
}
})
return thenPromise
}
/**
* 这个函数的作用是,当这个promises数组都是fuilfilled(成功)状态时,才执行then的成功回调函数,返回值全是这个数组的item的resolve结果
* 当这个promises数组中,出现非Promise的实例时,自动算fuilfilled
* 只要这个数组中出现了一个rejected(失败),执行then的失败回调函数,参数是这个失败的reject的参数
* @param {数组}} promises
* @returns
*/
static all(promises) {
const result = []
let count = 0
return new MyPromise((resolve, reject) => {
const addData = (index, value) => {
result[index] = value
count++
if (count === promises.length) resolve(result)
}
promises.forEach((promise, index) => {
if (promise instanceof MyPromise) {
promise.then(res => {
addData(index, res)
}, err => reject(err))
} else {
addData(index, promise)
}
})
})
}
/**
* 接收promise数组,非promise数组的自动算成功
* 哪个promise返回结果最快,就返回那个结果,无论成功或者失败
* @param {*} promises
*/
static race (promises){
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
if (promise instanceof MyPromise) {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(promise)
}
})
})
}
catch (onRejected){
if([this.STATUS[1],this.STATUS[0]].includes(this.PromiseResult)){ // pedding fulfilled 不能进入
return
}
// 之前提到过 catch 是 then(null,onRejected) 的语法糖
return this.then(null,onRejected)
}
initBind() {
// 修改 this.resolve 以及 this.reject 的this指向 MyPromise内部 避免发生隐式丢失
// 这里为什么不用call 或者 apply 呢 因为 bind 绑定的函数,不会立即执行
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
}
const p1 = new MyPromise((resolve,reject)=>{
console.log("执行MyPromise的构造函数");
setTimeout(resolve(22),0)
})
p1.then((res)=>{
console.log("执行then方法:",res);
})
console.log("主流程执行。。。")
附:compose函数实现
从《JS高级程序设计第四版》得知,可利用Promise链式调用的特性,实现一个compose函数
compose函数:组合函数,传入函数数组,对函数进行依次调用,此刻调用的参数是上一个调用函数的返回值
Promise的实现
function compose(...fns) {
return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x))
}
function addTwo(x) {return x + 2;}
function addThree(x) {return x + 3;}
function addFive(x) {return x + 5;}
let addTen = compose(addTwo, addThree, addFive);
addTen(8).then(console.log); // 18
普通版的compose实现
const compose = (x)=>{
return (...args)=>{
let result = null;
args.reduce((acc,cur,index)=>{
acc = cur(acc);
result = acc; // 做个赋值
return acc
},x)
return result;
}
}