1. 异步代码存在困境
-
下面的解决方案,可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
- 需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
- 对于不同的人、不同的框架设计出来的方案是不同的,就必须耐心去看别人的源码或者文档,以便可以理解它这个函数到底怎么用。
// 1.设计这样的一个函数 function execCode(counter, successCallback, failureCallback) { // 异步任务 setTimeout(() => { if (counter > 0) { // counter可以计算的情况 let total = 0 for (let i = 0; i < counter; i++) { total += i } // 在某一个时刻只需要回调传入的函数 successCallback(total) } else { // 失败情况, counter有问题 failureCallback(`${counter}值有问题`) } }, 3000) } // 2.ES5之前,处理异步的代码都是这样封装 execCode(100, (value) => { console.log("本次执行成功了:", value) }, (err) => { console.log("本次执行失败了:", err) })
2. Promise
-
Promise是一个类,可以翻译成 承诺、许诺 、期约;
-
当需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象
-
通过new创建Promise对象时,需要传入一个回调函数,称之为executor
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject
- 当调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数
- 当调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数
function execCode(counter) { const promise = new Promise((resolve, reject) => { // 异步任务 setTimeout(() => { if (counter > 0) { // counter可以计算的情况 let total = 0 for (let i = 0; i < counter; i++) { total += i } // 成功的回调 resolve(total) } else { // 失败情况, counter有问题 // 失败的回调 reject(`${counter}有问题`) } }, 3000) }) return promise } const promise = execCode(100) promise.then((value) => { console.log("成功有了结果: ", value) }) promise.catch((err) => { console.log("失败有了错误: ", err) }) // const promise2 = execCode(-100) // promise2.then(value => { // console.log("成功:", value) // }) // promise2.catch(err => { // console.log("失败:", err) // }) // 执行一次 execCode(255).then(value => { console.log("成功:", value) }).catch(err => { console.log("失败:", err) }) -
3. Promise三种状态
-
Promise划分成三个状态:
-
待定(pending): 初始状态,既没有被兑现,也没有被拒绝
- 当执行executor中的代码时,处于该状态
-
已兑现(fulfilled): 意味着操作成功完成
- 执行了resolve时,处于该状态,Promise已经被兑现
-
已拒绝(rejected): 意味着操作失败
- 执行了reject时,处于该状态,Promise已经被拒绝
-
-
Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:
-
通常会在Executor中确定Promise的状态:
- 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved)
- 通过reject,可以拒绝(reject)Promise的状态
const promise = new Promise((resolve, reject) => { // 注意: Promise的状态一旦被确定下来, 就不会再更改, 也不能再执行某一个回调函数来改变状态 // 1.待定状态 pending console.log("111111") console.log("222222") console.log("333333") // 2.兑现状态 fulfilled resolve() // 3.拒绝状态 rejected reject() }) promise.then(value => { console.log("成功的回调") }).catch(err => { console.log("失败的回调") }) -
-
注意!!!
- Promise状态只能从pending->rejected 或者pending ->fulfilled
- Promise的状态一旦被确定下来就不会再更改, 也不能再执行某一个回调函数来改变状态
4. resolve不同的值
-
如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数
-
如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态
-
如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态
const p = new Promise((resolve) => { // setTimeout(resolve, 2000) setTimeout(() => { resolve("p的resolve") }, 2000) }) const promise = new Promise((resolve, reject) => { // 1.普通值 // resolve([ // {name: "macbook", price: 9998, intro: "有点贵"}, // {name: "iPhone", price: 9.9, intro: "有点便宜"}, // ]) // 2.resolve(promise) // 如果resolve的值本身Promise对象, 那么当前的Promise的状态会有传入的Promise来决定 // resolve(p) // 3.resolve(thenable对象) resolve({ name: "kobe", then: function(resolve) { resolve(11111) } }) }) promise.then(res => { console.log("then中拿到结果:", res) })
5. 实例方法
5.1 then方法
then方法是Promise对象上的一个方法(实例方法):其实是放在Promise的原型上的 Promise.prototype.then
-
接受两个参数
- fulfilled的回调函数:当状态变成fulfilled时会回调的函数
- reject的回调函数:当状态变成reject时会回调的函数 (等同于 promise.catch)
-
多次调用
- 一个Promise的then方法是可以被多次调用的:每次调用都可以传入对应的fulfilled回调
- 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行
const promise = new Promise((resolve, reject) => { resolve("success") // reject("failure") }) // promise.then(res => { // }).catch(err => { // }) // 1.then参数的传递方法: 可以传递两个参数 // 这种写法也是可以的 // promise.then(res => { // console.log("成功回调~", res) // }, err => { // console.log("失败回调~", err) // }) promise.then(res => { console.log("成功回调~", res) }) promise.then(res => { console.log("成功回调~", res) }) promise.then(res => { console.log("成功回调~", res) }) promise.then(res => { console.log("成功回调~", res) }) -
返回值:返回一个Promise
- 当then方法中的回调函数本身在执行的时候,它处于pending状态
- 当then方法中的回调函数返回一个结果时,它处于fulfilled状态,并且会将结果作为resolve的参数
- 情况一:返回一个普通的值
- 情况二:返回一个Promise
- 情况三:返回一个thenable值
- 当then方法抛出一个异常时,那么它处于reject状态
const promise = new Promise((resolve, reject) => { resolve("aaaaaaa") // reject() }) // 1.then方法是返回一个新的Promise, 这个新Promise的决议是等到then方法传入的回调函数有返回值时, 进行决议 // Promise本身就是支持链式调用 // then方法是返回一个新的Promise, 链式中的then是在等待这个新的Promise有决议之后执行的 // promise.then(res => { // console.log("第一个then方法:", res) // return "bbbbbbbb" // }).then(res => { // console.log("第二个then方法:", res) // return "cccccccc" // }).then(res => { // console.log("第三个then方法:", res) // cccccccc // }) // promise.then(res => { // console.log("添加第二个then方法:", res) // }) // 2.then方法传入回调函数的返回值类型 const newPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("kobe") }, 3000) }) promise.then(res => { console.log("第一个Promise的then方法:", res) // 1.普通值 // return "bbbbbbb" // 2.新的Promise // return newPromise // 3.thenable的对象 return { then: function(resolve) { resolve("thenable") } } }).then(res => { console.log("第二个Promise的then方法:", res) // undefined })
5.2 catch方法
catch方法也是Promise对象上的一个方法(实例方法):也是放在Promise的原型上的 Promise.prototype.catch
-
一个Promise的catch方法是可以被多次调用的:
- 每次调用都可以传入对应的reject回调
- 当Promise的状态变成reject的时候,这些回调函数都会被执行
const promise = new Promise((resolve, reject) => { reject("failure") }) // 需要捕获异常,否则会报错 Uncaught (in promise) failure promise.then(res => { console.log("成功的回调:", res) }).catch(err => { console.log("失败的回调:", err) }) promise.catch(err => { console.log("失败的回调:", err) }) promise.catch(err => { console.log("失败的回调:", err) }) promise.catch(err => { console.log("失败的回调:", err) }) promise.catch(err => { console.log("失败的回调:", err) }) -
catch方法也是会返回一个Promise对象的,所以catch方法后面可以继续调用then方法或者catch方法:
- 如果希望后续继续执行catch,那么需要抛出一个异常
const promise = new Promise((resolve, reject) => { // reject("error: aaaaa") resolve("aaaaaa") }) // 1.catch方法也会返回一个新的Promise // promise.catch(err => { // console.log("catch回调:", err) // return "bbbbb" // }).then(res => { // console.log("then第一个回调:", res) // return "ccccc" // }).then(res => { // console.log("then第二个回调:", res) // }) // 2.catch方法的执行时机 promise.then(res => { console.log("then第一次回调:", res) // throw new Error("第二个Promise的异常error") return "bbbbbb" }).then(res => { console.log("then第二次回调:", res) throw new Error("第三个Promise的异常error") }).then(res => { console.log("then第三次回调:", res) }).catch(err => { console.log("catch回调被执行:", err) })
5.4 finally方法(ES9)
-
finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码。
-
注意:finally方法是不接收参数的
const promise = new Promise((resolve, reject) => { // pending // fulfilled resolve("aaaa") // rejected // reject("bbbb") }) promise.then(res => { console.log("then:", res) // foo() }).catch(err => { console.log("catch:", err) // foo() }).finally(() => { console.log("哈哈哈哈") console.log("呵呵呵呵") })
6. 类方法
6.1 resolve
-
如果希望将一个已知数据转成Promise来使用,这个时候可以使用
Promise.resolve方法来完成- Promise.resolve的用法相当于new Promise,并且执行resolve操作
-
resolve参数的形态:
- 参数是一个普通的值或者对象
- 参数本身是Promise
- 参数是一个thenable
// 类方法 const studentList = [] const promise = Promise.resolve(studentList) promise.then(res => { console.log("then结果:", res) })
6.2 reject
-
reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态
-
Promise.reject 的用法相当于new Promise,只是会调用reject
-
Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
const promise = Promise.reject("rejected error") promise.catch(err => { console.log("err:", err) })
6.3 all
-
将多个Promise包裹在一起形成一个新的Promise
-
新的Promise状态由包裹的所有Promise共同决定
- 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
- 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数
// 创建三个Promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p1 resolve") reject("p1 reject error") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p2 resolve") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 5000) }) // all:全部/所有 Promise.all([p1, p2, p3]).then(res => { console.log("all promise res:", res) }).catch(err => { console.log("all promise err:", err) })
6.4 allSettled
-
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
- 那么对于resolved的,以及依然处于pending状态的Promise,是获取不到对应的结果
-
在ES11(ES2020)中,添加了新的API
Promise.allSettled:- 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态,并且这个Promise的结果一定是fulfilled
- allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的
- 这个对象中包含status状态,以及对应的value值:
[{ status: , value/reason }]
- 这个对象中包含status状态,以及对应的value值:
// 创建三个Promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p1 resolve") reject("p1 reject error") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p2 resolve") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 5000) }) // 类方法: allSettled Promise.allSettled([p1, p2, p3]).then(res => { console.log("all settled:", res) })
6.5 race
-
竞赛: 有一个有结果, 不管这个结果是fulfilled还是rejected
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p1 resolve") // reject("p1 reject error") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p2 resolve") reject("p2 reject error") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 5000) }) // 特点: 会等到一个Promise有结果(无论这个结果是fulfilled还是rejected) Promise.race([p1, p2, p3]).then(res => { console.log("race promise:", res) }).catch(err => { console.log("race promise err:", err) })
6.6 any
-
any方法是ES12中新增的方法,和race方法是类似的:
-
any方法会等到一个fulfilled状态,才会决定新Promise的状态;
-
如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
-
-
如果所有的Promise都是reject的,那么会报一个
AggregateError的错误
-
总结
- 任意: 等到一个fulfilled, 如果都不是fulfilled生成一个错误对象
const p1 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p1 resolve") reject("p1 reject error") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p2 resolve") reject("p2 reject error") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p3 resolve") reject("p3 reject error") }, 5000) }) // 类方法: any方法 Promise.any([p1, p2, p3]).then(res => { console.log("any promise res:", res) }).catch(err => { console.log("any promise err:", err) // any promise err: AggregateError: All promises were rejected })
7. 总结
7.1 promise的特点
-
对象的状态不受外界影响
- promise有三种状态
pending(进行中)fulfilled(已成功)rejected(已失败),只有异步操作的结果,才可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- promise有三种状态
-
一旦从等待状态变成为其他状态就永远不能更改状态
- promise只有两种状态改变:
pending(进行中)-->fulfilled(已成功)pending(进行中)-->rejected(已失败)
- promise只有两种状态改变:
7.2 Promise的优点
-
让回调函数变成了规范的链式写法,并且有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到他们的状态都改变以后,在执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一制定处理方法…
-
他的状态一旦改变,无论何时查询,都能得到这个状态。这意味着无论何时为promise实例添加回调函数,该函数都能正确执行。
-
传统写法的话都通过监听事件来执行回调函数,一旦错过了事件,再添加回调函数是不会执行的。
7.3 Promise的缺点
- 一旦新建Promise就会立即执行,无法中途取消。
- 如果不设置回调函数callback,Promise内部抛出的错误,就不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。