Promise学习笔记
Promise学习笔记
参考文章:https://juejin.cn/post/7063377198014529572
一.介绍
1.1 什么是Promise
Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。是ES6新增的一个:异步编程的解决方案,相对传统的解决方案,他更合理和强大。本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。
再出现Promise之前如果一个操作依赖另一个操作的成功结果而执行的话我们这样写:
doSomething(function (result) {
doSomethingElse(
result,
function (newResult) {
doThirdThing(
newResult,
function (finalResult) {
console.log("Got the final result: " + finalResult);
},
failureCallback,
);
},
failureCallback,
);
}, failureCallback);
现在,我们可以把回调绑定到返回的 Promise 上,形成一个 Promise 链:
doSomething()
.then(function (result) {
return doSomethingElse(result);
})
.then(function (newResult) {
return doThirdThing(newResult);
})
.then(function (finalResult) {
console.log("Got the final result: " + finalResult);
})
.catch(failureCallback);
Tips:作为一个前端小白,我的理解就是:用Promise,可以把一层一层的if-else结构,搞成链式的.then().then().then().catch()的形式,优雅就完事儿了~
1.2 约定
不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:
- 在本轮 事件循环 运行完成之前,回调函数是不会被调用的。
- 即使异步操作已经完成(成功或失败),在这之后通过
then()添加的回调函数也会被调用。 - 通过多次调用
then()可以添加多个回调函数,它们会按照插入顺序进行执行。
Promise 很棒的一点就是链式调用(chaining)。
1.3 链式调用
连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。
Tips:换成人话就是:我的需求中,有一个函数需要用到另一个函数的运行结果。
比如去银行取钱,ATM得判断你先插卡了,才能输密码,然后在判断你的密码对不对。所以判断密码对不对依赖于你输没输密码,而输没输密码又依赖于你插卡了没
之前非常恶心的写法:回调地狱
//调用顺序
function 插卡(){
if(插卡了){
function 输入密码(){
if(输入了){
function 判断密码对不对(){
if(密码正确){
return 给你钱
}else{
return 报警!
}
}
}else{
return 你插卡了不输密码在这瞅啥呢?
}
}
}else{
return 你不插卡在这瞎按啥?
}
看着就刺激,用了Promise之后:优雅~
插卡().then( 输入密码() ).then( 判断密码对不对() ).catch(前面有阶段出错了!);
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,
() => x比() => { return x; }更简洁一些,但后一种保留return的写法才支持使用多个语句。)。
还有一些花式操作的例子:Catch 的后续链式操作
有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。请阅读下面的例子:
new Promise((resolve, reject) => {
console.log("插卡输入密码");
resolve();
})
.then(() => {
throw new Error("你卡里没钱!");
console.log("草没钱了,这咋整");
})
.catch(() => {
console.log("要不直接抢银行吧");
})
.then(() => {
console.log("有钱没钱也抢了!反正银行里钱多!");
});
--------------------------------------------------------------
//输出结果
插卡输入密码
要不直接抢银行吧
有钱没钱也抢了!反正银行里钱多!
1.4 错误传递
在之前的回调地狱示例中,你可能记得有 3 次 failureCallback 的调用,而在 Promise 链中只有尾部的一次调用。通常,一遇到异常抛出,浏览器就会顺着 Promise 链寻找下一个 onRejected 失败回调函数或者由 .catch() 指定的回调函数。这和以下同步代码的工作原理(执行过程)非常相似。
try {
let result = syncDoSomething();
let newResult = syncDoSomethingElse(result);
let finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch (error) {
failureCallback(error);
}
二.快速开始
2.1 构造函数
通过 Promise 的构造器从零开始创建 Promise。通常,Promise 的构造器接收一个执行函数 (executor),我们可以在这个执行函数里手动地 resolve 和 reject 一个 Promise。
return new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve('成功')
}else{
reject('失败')
}
}, 2000)
})
executor 传入的 resolve 和 reject 本身都是函数。其作用分别为:
-
resolve- 把 Promise 的状态从进行中变为成功状态。 -
reject- 把 Promise 的状态从进行中变为拒绝状态。
Promise的三种状态:
-
pending :进行中,表示 Promise 还在执行阶段,没有执行完成。
-
fulfilled:成功状态,表示 Promise 成功执行完成。
-
rejected:拒绝状态,表示 Promise 执行被拒绝,也就是失败。
Promise 的状态,只可能是其中一种状态,从进行中变为成功或失败状态之后,状态就固定了,不会再发生改变。
2.2 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)
})
.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)
})
2.3 Promise 的实例方法
通过then方法可以对 Promise 中的resolve进行处理。then方法的返回值是一个 Promise 实例
new Promise(resolve => {
resolve('你好')
}).then(res => console.log(res)) // 会打印你好
同一个 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))
2.3.1 then
通过then方法可以对 Promise 中的resolve进行处理。then方法的返回值是一个 Promise 实例
new Promise(resolve => {
resolve('你好')
}).then(res => console.log(res)) // 会打印你好
同一个 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 的将会传递给下一个 Promise。
const promise = new Promise(resolve => {
resolve('你好')
})
promise
.then(() => {
return {
then(resolve) {
return resolve('success')
},
}
})
.then(msg => {
// 打印 success
console.log(msg)
})
2.3.2 catch
除了then方法的第二个参数来捕获reject错误之外,还可以通过catch方法,catch 返回一个 Promise,catch方法也是可以多次调用的,只要 Promise 实例的状态为 rejected,那么就会调用catch方法
onst 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)
})
catch 方法也会返回一个Promise实例,返回值的情况:
- 普通值,将作为
resolve的参数
2.3.3 finally
finally 是 ES9(ES2018) 新增的一个特性,无论一个Promise实例是fulfilled或rejected,finally都会执行。
finally 不接收参数。
const promise = new Promise((resolve, reject) => {
reject('error')
})
promise
.then(res => {
console.log('res:', res)
})
.catch(err => {
console.log(('err', err))
})
.finally(() => {
console.log('finally code execute')
})
2.4 静态方法
2.4.1 resolve
如果我们想要将一个现成的数据转换为一个 Promise 实例,那么你可以这么做:
const foo = {
name: 'alex',
}
function bar(obj) {
return new Promise(resolve => {
resolve(obj)
})
}
bar(foo).then(res => {
console.log(res)
})
还可以直接类方法resolve(),使用Promise.resolve()相当于new Promise(resolve => { resolve() })
function bar(obj) {
return Promise.resolve(obj)
}
resolve 参数形态:
- 参数本身是 Promise
- 参数是原始值/对象
- 参数是一个 thenable
2.4.2 reject
与Promise.resolve()方法逻辑基本相同,只不过Promise.reject()相当于创建一个 Promise 实例,并且 rejected 了
Promise.reject('error').catch(error => {
console.log('error', error)
})
注意:与
Promise.resolve()不同的是,Promise.reject()无论传递什么参数都会原样输出
2.4.3 all
Promise.all()接收一个Promise[],返回一个Promise实例,当所有的 Promise 执行完毕并且都是fulfilled时,该实例的状态才会变为fulfilled,只要队列中有一个实例的状态是rejected,那么该实例的状态也会变为`rejected
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 = [
genPromise(),
new Promise((resolve, reject) => {
reject('error1')
}),
new Promise((resolve, reject) => {
reject('error2')
}),
]
Promise.all(promiseArr)
.then(res => {})
.catch(err => {
// error 1
console.log(err)
})
2.4.4 allSettled
all方法是有缺陷的,如果在 Promise 队列中有一个状态是 rejected,那么我们就无法获取到其他 fullfilled 以及 pending 的 Promise 实例了。
针对这一情况,在 ES11(ES2020) 中新增了一个 API,Promise.allSettled()
- 该方法返回的 Promise 实例,会在所有 Promise 实例执行完毕后,状态方可变为
fulfilled,并且只会是fulfilled - 无论队列中的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)
})
2.4.5 race
Promise.race()同样接收一个 Promise 队列,返回一个 Promise 实例。该方法会对队列任务完成情况进行监听,如果某一个任务最先完成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.4.6 any
Promise.any()是 ES12 新增的特性,和Promise.race()类似,区别在于:
- any 方法会等待一个
fulfilled状态,才会决定返回 Promise 实例的状态 - 如果队列中所有的实例都是
rejected状态,那也需要等到所有执行完毕后才会决定返回的 Promise 实例的状态
简单理解来说,Promise.any()会等待第一个fulfilled的 Promise ,如果队列中没有fulfilled,那么就会返回一个错误
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]

浙公网安备 33010602011771号