前言
Promise是开发过程中使用频率很高的一个对象,但是我们不一定都能对这个对象描述的清楚。因此,在本篇文章中,我将对根据各方资料做一个汇总,便于大家和自己的回顾学习
一、Promise介绍
1.1 什么是Promise
Promise,这个单词对应的中文意思是“承诺,诺言”;- 是JS中的一个原生对象,是一种异步编程的解决方案,可以替换传统的函数解决方案
- 是一个类
Promise是一个状态机,分为三种状态pending:待定状态,分为 executor 后,处于该状态fulfilled:兑现状态,调用resolve()后,Promise的状态更改为 fulfilled,且无法再次更改rejected:拒绝状态,调用reject()后,Promise的状态更改为 rejected,且无法更改
function request() {
const flag = Math.random() <= 0.5 ? true : false
return new Promise((resolve, reject) => {
setTimeout(() => {
if (flag) {
resolve('成功的消息')
return
}
reject('失败的消息')
}, 2000)
})
}
console.log('请求开始')
request()
.then(msg => console.log(msg), err => console.log(err))
1.2 背景
- 为了解决回调地狱的问题
- 目前是前端最好的异步解决方案
-
什么是回调地狱问题?
如果在回调函数中再传入一个函数,就会出现一个嵌套结构,如此层层嵌套,就会形成回调地狱。
简单举个场景:业务中有时在请求数据会出现这种情况,需要发送多个Ajax请求,假设现在有1, 2, 3, 4这四个请求,4需要3的响应数据中的某个数据作为传参,3需要2的响应数据中的某个数据作为传参,2需要1的响应数据中的某个数据作为传参,如此层层嵌套就是回调地狱。这样的代码维护性差,且可读性也差
//有多个异步任务,要求需要同时拿到所有异步任务的结果,下边就是用回调地狱 $.get("url", (res1) => { conosle.log(res1) $.get("url+res1", (res2) => { conosle.log(res2) $.get("url+res2", (res3) => { conosle.log(res3) $.get("url+res3", (res4) => { conosle.log(res4) }) }) }) }) -
Promise是如何解决回调地狱的?
- Promise构造函数接收一个函数作为参数,我们需要处理的异步任务就卸载该函数体内,该函数的两个参数是 resolve和reject。异步任务执行成功时调用
resolve函数返回结果,反之调用reject - Promise对象的
then方法用来接收处理成功时响应的数据,catch方法用来处理失败时相应的数据 - Promise的链式编程可以保证代码的执行顺序,前提是每一次在
then做完处理后,一定要return一个Promise对象,这样才能在下一次then时接收到对象
function fn(str){ var p = new Promise(function(resolve,reject){ //处理异步任务 var flag=true; setTimeout(function(){ if(flag){ resolve(str) } else{ reject('操作失败') } }) }) return p; } fn('武林要以和为贵') .then((data)=>{ console.log(data); return fn('要讲武德'); }) .then((data)=>{ console.log(data); return fn('不要搞窝里斗') }) .then((data)=>{ console.log(data); }) .catch((data)=>{ console.log(data); }) - Promise构造函数接收一个函数作为参数,我们需要处理的异步任务就卸载该函数体内,该函数的两个参数是 resolve和reject。异步任务执行成功时调用
1.3 Promise的用法
- 声明:
new Promise((resolve, reject) => {}) - 出参:
- resolve:将状态从
未完成变为成功,在异步操作成功时调用,并将异步操作的结果作为参数传递出去 - reject:将状态从
未完成变为失败,在异步操作失败时调用,并将异步操作的错误作为参数传递出去
- resolve:将状态从
1.3.1 resolve的参数
resolve 传入的参数情况:
-
如果传入的普通的值或对象,那么就会被传递到 then 的参数的
-
如果传入的是一个Promise,那么当前的 Promise 的状态将会由传入的 promise 来决定
const newPromise = new Promise((resolve, reject) => { resolve('success') }) new Promise((resolve, reject) => { // 当前 Promise 的状态由传入的 Promise 去决定 resolve(newPromise) }) .then(res => { console.log('res', res) // res success }) .catch(err => { console.log('err', err) }) -
如果传入的是一个对象,且该对象实现了then方法(thenable),也会执行该then方法,并且由该then方法决定后续的状态
new Promise((resolve, reject) => { // 如果 resolve 传入的是对象,且该对象实现了 then 方法 // 则该 Promise 的状态由 then 方法决定 resolve({ then(resolve, reject) { reject('error') }, }) }) .then(res => { console.log('res', res) }) .catch(err => { console.log('err', err) // err error })
二、Promise的方法
2.1 Promise 的实例方法
2.1.1 then 方法
通过 then方法可以对 Promise中的resolve进行处理。then方法的返回值是一个Promise实例
```js
new Promise(resolve => {
resolve('你好')
})
.then(res => {
console.log(res) // 你好
})
```
-
多次调用
then方法同一个Promise实例可以调用多个
then方法,当 Promise 中resolve被回调时,所有 then 方法传入的回调函数都会被调用const promise = new Promise(resolve => { resolve('你好') }) // 同时调用 promise.then(res => console.log(res)) // 你好 promise.then(res => console.log(res)) // 你好 promise.then(res => console.log(res)) // 你好 -
then 方法传入的回调函数可以有返回值
-
返回的是普通值,那么这个普通值将作为一个新的 Promise 的resolve 的值
const promise = new Promise(resolve => { resolve('你好') }) promise.then(() => 'then').then(res => console.log(res)) // 打印 then // promise.then(() => 'then') 相当于 promise.then(() => { return new Promise(resolve => { resolve('then') }) }) -
返回的是Promise,那么就可以再次调用
then方法const promise = new Promise(resolve => { resolve('你好') }) promise .then(() => { return new Promise(resolve => { setTimeout(() => { resolve('success') }, 2000) }) }) .then(msg => { // 2 秒后打印 success console.log(msg) }) -
返回的是一个对象,并且该对象实现了 thenable,该 then 函数有两个参数
resolve、reject,则 resolve 的将会传递给下一个 Promiseconst promise = new Promise(resolve => { resolve('你好') }) promise .then(() => { return { then(resolve) { return resolve('success') }, } }) .then(msg => { // 打印 success console.log(msg) })
-
2.1.2 catch 方法
处理then方法的第二个参数来捕获reject错误之外,还可以通过catch方法,catch返回一个Promise
const promise = new Promise((resolve, reject) => {
reject('error')
})
promise.then(undefined, err => {
// 打印 error
console.log(err)
})
// 但是这种写法不太符合`promise/a+`规范
promise.catch(err => {
// 打印 error
console.log(err)
})
// 下面是符合`promise/a+`规范的写法
promise
.then(() => {})
.catch(err => {
console.log(err)
})
// 已知 then 方法也可以返回一个 promise,因此在 then 后面追加 catch,以此来捕获 rejected 的情况,更加具有语义化
catch方法也是可以多次调用的,只要 Promise 实例的状态为 rejected,那么就会调用catch方法
const promise = new Promise((resolve, reject) => {
reject('error')
})
// 这两个 catch 都会调用
promise.catch(err => {
console.log(err)
})
promise.catch(err => {
console.log(err)
})
catch方法的返回值:
catch 方法也会返回一个 Promise 实例,返回值的情况:
- 普通值,将作为
resolve的参数
2.1.3 finally 方法
finally 是ES9(ES2018)新增的一个特性,无论一个 Promise实例是fulfilled或rejected,finally都会执行。
const promise = new Promise((resolve, reject) => {
if(Math.ceil(Math.random()*10) % 2) {
resolve("success")
} else {
reject('error')
}
})
promise
.then(res => {
console.log('res:', res)
})
.catch(err => {
console.log(('err', err))
})
.finally(() => {
console.log('finally code execute')
})
finally 不接收任何参数,这意味着没有办法知道,前面的Promise状态到底是fulfilled还是rejected。这表明,finally 方法里的操作,应该是与状态无关的,不依赖于Promise的执行结果,可以避免在then和catch处理器中国重复编写代码
finally本质上是then方法的特例。
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
2.2 Promise的类方法
2.2.1 resolve 方法
Promise.resolve()静态方法将给定的值转换为一个Promise。如果该值本身就是一个 Promise,那么该 Promise 将被返回;如果该值是一个 thenable 对象,Promise.resolve()将调用其then方法及其两个回调函数;否则返回的 Promise 将会以该值兑现
new Promise(resolve => {
resolve({ name: "张三" })
})
.then(res => {
console.log(res)
})
还可以直接类方法resolve(),使用Promise.resolve()相当于new Promise(resolve => { resolve() })
Promise.resolve({name: '张三'})
.then(res => {
console.log(res)
})
resolve参数形态:
- Promise实例:原封不动地返回入参
- thenable对象:将此对象转为 Promise 对象并返回(thenable为包含
then()的对象,执行then()相当于执行此对象的then()) - 原始值/对象(不带
then()的对象):将此对象转为Promise对象返回 - 不带参数:返回Promise对象,状态为
resolved
2.2.2 reject 方法
与Promise.resolve()方法逻辑基本相同,只不过Promise.reject()相当于创建一个 Promise 实例,并且 rejected 了
Promise.reject('error').catch(error => {
console.log('error', error)
})
参数形态:Promise.reject()无论传递什么参数都会原样输出
Promise.reject(
new Promise(resolve => {
resolve('hello')
})
).catch(err => {
// 原样打印 Promise 实例
console.log('err', err)
})
2.2.3 all 方法
将多个实例包装成一个新实例,返回全部实例状态变更后的结果数组。
-
Promise.all()的入参是Promise[], 数组的每个成员都是一个Promise实例 -
如果 Promise 队列中所有的实例状态都是
fulfilled,那么Promise.all()返回的实例的状态就会变为fulfilled,并且resolve()的参数是一个数组,按照顺序放置队列中每个 Promise 成功后的结果let i = 0 function genPromise() { return new Promise(resolve => { resolve(`success${(i = i + 1)}`) }) } const promiseArr = [genPromise(), genPromise(), genPromise()] Promise.all(promiseArr) .then(res => { // [ 'success1', 'success2', 'success3' ] console.log('res', res) }) -
如果队列中 Promise 实例有一个是
rejected,那么Promise.all()返回的实例就会变为rejected状态,并且reject()参数是队列中第一个rejected的返回值const promiseArr = [ new Promise(resolve => { resolve('success') }), new Promise((resolve, reject) => { reject('error1') }), new Promise((resolve, reject) => { reject('error2') }), ] Promise.all(promiseArr) .then(res => {}) .catch(err => { // error 1 console.log(err) }) -
缺点:如果在 Promise 队列中有一个状态是 rejected,那么我们就无法获取到其他 fullfilled 以及 pending 的 Promise 实例了。
有点一荣俱荣,一损俱损的感觉
-
适用场景:
- 彼此相互依赖,其中任何一个被reject,其它都失去了实际价值
2.3.4 allSettled 方法
将多个实例包装成一个新实例,返回全部实例状态变更后的结果数组。
Promise.allSettled()是ES11(ES2020) 中新增了一个 API,是为了解决all方法的缺陷,算是 all 方法的优化版
Promise.allSettled()的入参是Promise[], 数组的每个成员都是一个Promise实例- 特点:
- 该方法返回的 Promise 实例,会在所有 Promise 实例执行完毕后,状态方可变为
fulfilled,并且只会是fulfilled - 无论队列中的 Promise 实例的状态如何,都能获取到结果
- 打印的结果,会包含状态、值和原因的
- 该方法返回的 Promise 实例,会在所有 Promise 实例执行完毕后,状态方可变为
const promiseArr = [
new Promise((resolve, reject) => {
resolve('success1')
}),
new Promise((resolve, reject) => {
reject('error')
}),
new Promise((resolve, reject) => {
resolve('success2')
}),
]
Promise.allSettled(promiseArr).then(res => {
// res [
// { status: 'fulfilled', value: 'success1' },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 'success2' }
// ]
console.log('res', res)
})
- 适用场景:
- 彼此不依赖,其中任何一个被 reject,对其它不影响
- 期望知道每个 promise 的执行结果
2.3.5 race 方法
将多个实例包装成一个新实例,返回全部实例状态优先变更后的结果(先变更后返回)
- 入参:具有
Iterator接口的数据结构 - 成功失败:如果某一个任务最先完成
fulfilled/rejected,那么返回的实例的状态也会变成对应的fulfilled/rejected,同时获取到最先完成的结果
const promiseArr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success2')
}, 3000)
}),
]
Promise.race(promiseArr)
.then(res => {
console.log('res', res)
})
.catch(err => {
console.log('err', err)
})
// 最终打印 res success1
// 如果第二个任务最先完成,那么就会打印 err error
2.3.6 any 方法
Promise.any()是 ES12 新增的特性,和Promise.race()类似,区别在于:
- any 方法会等待一个
fulfilled状态,才会决定返回 Promise 实例的状态 - 如果队列中所有的实例都是
rejected状态,那也需要等到所有执行完毕后才会决定返回的 Promise 实例的状态
const promiseArr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 2200)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success2')
}, 3000)
}),
]
Promise.any(promiseArr)
.then(res => {
console.log('res', res)
})
.catch(err => {
console.log('err', err)
})
// 遇到第一个 fulfilled,就会转变返回的 Promise 实例的状态
// 如果所有的都是 rejected,那么只有所有执行完毕后,返回的 Promise 实例才会转变
// 并且会抛出一个错误:[AggregateError: All promises were rejected]
三、Promise 的使用
3.1 Promise的链式调用
制作一个模拟网络请求:
- 第一次返回 a,
- 修改返回的结果为 aa,作为第二次网络请求返回的结果。
- 修改结果为 aaa,作为第三次返回结果。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("a");
}, 1000);
})
.then((res) => {
console.log("1秒后打印, res1", res); //1秒后打印 a
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res + "a");
}, 1000);
});
})
.then((res) => {
console.log("2秒后打印, res", res); //2秒后打印 aa
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(res + "a");
}, 1000);
});
})
.then((res) => {
console.log("3秒后打印, res3", res); //3秒后打印 aaa
});
这种情况其实就是接口的多层嵌套调用,Promise 可以把多层嵌套按照线性的方式进行书写,非常优雅。我们把 Promise 的多层嵌套调用就叫做链式调用
3.2 Promise 嵌套使用的简写
promise传入的函数参数reject是一个非必传的参数,如果不需要处理失败时的结果时,我们可以省略掉 reject 。代码如下:
// 简化1
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("a");
}, 1000);
})
.then((res) => {
console.log("res1", res);
return new Promise((resolve) => resolve(res + "a"));
})
.then((res) => {
console.log("res", res);
return new Promise((resolve) => resolve(res + "a"));
})
.then((res) => {
console.log("res3", res);
});
Promise 嵌套使用时,内层的 Promise 可以省略不写,所以我们可以直接把 Promise 相关的去掉,直接返回,代码如下:
//简化2
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("a");
}, 1000);
})
.then((res) => {
return res + "a";
})
.then((res) => {
return res + "a";
})
.then((res) => {
console.log("res3", res);
});
有的同学就在想,怎么都是成功状态的举例和简写,我们的失败状态catch可以简写吗?
答案是肯定的,我们简化为2层嵌套,与上述功能一致。
new Promise((resolve, reject) => {
setTimeout(() => {
reject("a");
}, 1000);
})
.catch((err) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(err + "a");
}, 1000);
});
})
.catch((err) => {
console.log("err", err);
});
//简写1
new Promise((resolve, reject) => {
setTimeout(() => {
reject("a");
}, 1000);
})
.catch((err) => {
return new Promise((resolve, reject) => reject(err + "a"));
})
.catch((err) => {
console.log("err", err);
});
//简写2
new Promise((resolve, reject) => {
setTimeout(() => {
reject("a");
}, 1000);
})
.catch((err) => {
throw err + "a"; // 失败简写省略掉Promise时,使用的 throw 抛出异常。
})
.catch((err) => {
console.log("err", err);
});
注意:失败简写省略掉Promise时,使用的 throw 抛出异常。
四、手写 Promise 的实现
有空的时候再研究补上...