[TOC] #### 1. 异步请求的方式 --- 封装一个网络请求方法,请求成功之后将结果返回,以下代码显然是不行的 ```javascript function requestData() { // 模拟网络请求 setTimeout(() => { // 拿到请求结果 const res = { code: 200, msg: '请求成功', data: { id: 1 } } // 当请求成功之后,肯定想要将结果返回出去 if (res.code == 200) { console.log(res.data); return res.data // 你可能会写,将将结果返回 } else { // 请求失败 } }, 3000) } const result = requestData() console.log(result) // undefined ``` 以前对于这种情况,也是有解决方案的,通常是使用回调函数,这种回调方式有很多弊端: + 如果是自己封装的,那么必须要自己设计并使用好 callback 名称 + 如果是别人封装的或一些第三方库,那么必须去看别人的源码或文档,才知道它这个函数如何去获取到结果 ```javascript function requestData(url, success, error) { setTimeout(() => { const res = { code: 201, msg: '请求成功', data: { id: 1 } } if (res.code == 200) { success(res.data) } else { error('请求失败') } }, 3000) } requestData('xx', data => { console.log(data) }, err => { console.log(err) }) ``` #### 2. Promise 的基础用法 --- 更好的方案是使用 Promise(翻译:承诺),它规范好了所有的代码编写逻辑 什么是 Promise ?Promise 的 API 是怎样的 ? + Promise 是一个类,可以翻译成:承诺、许诺 + 它会给调用者一个承诺:待会儿我会给你回调数据 + 通过 new 创建 Promise 对象时,需要传入一个回调函数,我们称之为 executor (执行器) + executor 在 Promise 创建时立即同步执行,并且给传入另外两个回调函数 resolve, reject + 当我们调用 resolve 回调函数时,会执行 Promise 对象的 then 方法传入的回调函数 + 当我们调用 reject 回调函数时,会执行 Promise 对象的 catch 方法传入的回调函数 ```javascript function foo() { return new Promise((resolve, reject) => { console.log('发送网络请求'); setTimeout(() => { const res = { code: 200, msg: '请求成功', data: { id: 1 } } if (res.code == 200) { resolve(res.data) } else { reject('请求失败') } }, 2000) }); } const fooPromise = foo() // 调用 resolve 时触发 fooPromise.then(res => { console.log('then', res) }) // 调用 reject 时触发 fooPromise.catch(res => { console.log('catch', res) }) ``` #### 3. Promise 的三种状态 --- Promise 的使用过程,我们可以将它划分为三个状态: + 待定(pending):初始状态,即没有被兑现,也没有被拒绝,当执行 executor 中的代码时,处于该状态 + 已兑现(fulfilled):意味着操作成功完成,执行了 resolve,处于该状态 + 已拒绝(rejected):意味着操作失败,执行了 reject,处于该状态 并且状态一旦确定下来,那么就是不可更改的(状态锁定) ```javascript new Promise((resolve, reject) => { resolve('请求成功') reject('错误信息') }).then(res => { console.log('then', res); }, err => { console.log('err', err); }) ``` #### 4. resolve 方法的参数 --- resolve 方法的参数类型: + 传入一个普通的值或对象,那么这个值会作为 then 回调的参数 + 传入另外一个 Promise,那么当前 Promise 状态将由传入的新 Promise 来决定(相当于状态进行了移交) + 传入的是一个对象,并且对象上有 then 方法,那么会执行该方法,根据 then 方法的结果来决定 Promise 的状态 传入一个普通的值或对象,是我们平时开发中使用最多的情况 ```javascript new Promise(resolve => { resolve({ code: 200, msg: 'success' }) }).then(res => { console.log(res) // {code: 200, msg: 'success'} }) ``` 传入另外一个 Promise,当前 Promise 状态将由传入的新 Promise 来决定。平时用的不多,但是面试很可能会问 ```javascript const newPromise = new Promise((resolve, reject) => { reject('发生错误') }) new Promise(resolve => { resolve(newPromise) }).then(res => { console.log(res) }, err => { console.log(err) // 发生错误 }) ``` 传入一个实现了 then 方法的对象,那么也会执行该 then 方法,并且该 then 方法决定 Promise 状态 ```javascript const object = { then(resolve, reject) { resolve('object resolve'); // reject('object reject'); } } new Promise(resolve => { // 传入一个包含 then 方法的对象 resolve(object) }).then(res => { console.log(res) // object resolve }, err => { console.log(err) // object reject }) ``` #### 5. promise 对象的 then 方法 --- then 方法是 Promise 对象上的一个方法,它其实是放在 Promise 的原型上的 `Promise.prototype.then` ```javascript // 查看 Promise 有哪些对象方法 console.log(Object.getOwnPropertyDescriptors(Promise.prototype)) ``` 思考一下,使用构造函数 Promise 创建对象时,其内部发生了什么,执行逻辑是怎么样的 ? ```javascript new Promise((resolve, reject) => { }) // 创建 promise 对象时,传入一个 executor 执行器,其参数又是两个函数,手写示例: class Person { constructor(executor) { const resolve = () => { } const reject = () => { } executor(resolve, reject) } } new Person((resolve, reject) => { }) ``` 1、同一个 Promise 可以被多次调用 then 方法:当 resolve 方法被回调时,所有的 then 方法传入的回调函数都会被调用 ```javascript const promise = new Promise((resolve, reject) => { resolve('hello') // 调用 resolve 方法,外面的三个 then 方法都会被调用 }) promise.then(res => { console.log('res1: ' + res) // res1: hello }) promise.then(res => { console.log('res2: ' + res) // res2: hello }) promise.then(res => { console.log('res3: ' + res) // res3: hello }) ``` 2、then 方法传入的回调函数是可以有返回值的(对应 resolve 方法参数用法): + 如果返回的是一个普通值(数值/字符串/普通对象/undefined),那么这个值会被作为一个新的 Promise 的 resolve 值 + 如果返回的是一个 Promise,它会决定当前 then 方法返回的 Promise 状态 + 如果返回的是一个包含 then 方法的对象,则根据 then 方法的结果来决定 Promise 的状态 返回的是一个普通值 ```javascript new Promise(resolve => resolve('hello')).then(res => { // 返回的是一个普通值,会被作为一个新的 Promise 的 resolve 值 return 'world' // 相当于 // return Promise.resolve('world') // 如果没有返回值,相当于返回了 undefined,这个和普通函数的返回值是一样的 // return undefined }).then(res => { console.log(res) // world }) ``` 返回值的是一个 Promise ```javascript new Promise(resolve => resolve('hello')).then(res => { // 返回的是一个 Promise,它会决定当前 then 方法返回的 Promise 状态 return new Promise((resolve, reject) => { setTimeout(() => { resolve('liang') }, 3000) }) }).then(res => { console.log(res) // liang }) ``` 返回的是一个包含 then 方法的对象 ```javascript new Promise(resolve => resolve('hello')).then(res => { // 返回的是一个包含 then 方法的对象,由这个 then 决定返回的 Promise 的状态 return { then(resolve, reject) { resolve('liang') } } }).then(res => { console.log(res) // liang }) ``` #### 6. promise 对象的 catch 方法 --- 1、当 executor 抛出异常时,也是会调用错误捕获的回调函数的 ```javascript new Promise((resolve, reject) => { reject('reject status') // 当 executor 抛出异常时,也是会调用错误(拒绝)捕获的回调函数的 // throw new Error('reject info') }).then('', error => { // then 的第二个参数是用来处理 reject() 回调的 console.log(error); }) ``` 2、通过 catch 方法来传入错误(拒绝) 捕获的回调函数 ```javascript new Promise((resolve, reject) => { reject('reject status') }).catch(error => { console.log(error) // reject status }) ``` 当我们学习过 then 方法的连用后,再来看下面这段代码,对输出结果你可能会有疑问 ```javascript // 按照上面我们的理解,此时应该有两个 Promise // 一个是 new Promise,一个是 then 方法中的 Promise,catch 处理的不应该是 then 方法中的 Promise 吗 ? new Promise((resolve, reject) => { reject('reject status') }).then(res => { return 'hello' }).catch(error => { console.log(error) // reject status }) ``` 这里的 catch 其实是优先捕获的 new Promise 的异常,再尝试捕获 then 方法的 Promise 异常(算是 ES6 的语法糖) ```javascript new Promise((resolve, reject) => { reject('reject status') }).then(res => { return 'hello' }).catch(error => { console.log(error) // reject status }) new Promise((resolve, reject) => { resolve('success') }).then(res => { return Promise.reject('then error') }).catch(error => { console.log(error) // then error }) ``` 3、拒绝捕获的问题(调用时有异常发生,但是没有捕获) ```javascript const p = new Promise((resolve, reject) => { // 抛出异常给调用者 reject('reject status') }) // 每次独立的调用,互不影响,每次调用时都应该对异常进行处理,否则会报错 // 这里调用时有异常,但是没有捕获,所以会抛出异常 p.then(res => { }) // 这里捕获了,不会报错 p.catch(err => { console.log(err); }) // 这里调用时没有捕获,所以会抛出异常 p.then(res => { }).catch(err => { console.log(err); }) ``` 4、catch 方法的返回值 catch 方法的返回值其实和 then 方法的返回值用法相同:返回一个普通值、返回一个 Promise 对象、包含 then 方法的对象 ```javascript new Promise((resolve, reject) => { reject('reject status') }).then(res => { console.log(res) }).catch(err => { console.log(err) // reject status return 'catch return' }).then(res => { console.log(res) }).catch(err => { console.log(err) // catch return }) ``` #### 7. promise 对象的 finally 方法 --- finally 是在 ES9 中新增的特性,表示无论 Promise 对象无论变成 fulfilled 还是 rejected 状态,最终都是会被执行的代码 finally 方法是不接收参数的,只要状态发生改变就会执行。执行顺序:先执行 then 或 catch,然后再执行 finally 方法 ```javascript new Promise((resolve, reject) => { reject() }).then(res => { console.log('then') }).catch(err => { console.log('catch') }).finally(() => { console.log('finally execute') }) ``` #### 8. Promise 类的 resolve 方法 --- 前面我们用的 then、catch、finally 方法都属于 Promise 的实例方法,都是存放在 Promise 的 prototype 上的 接下来,学习一下 Promise 类的方法,也就是可以通过 `Promise.方法名` 调用的方法 ```javascript // 需求:将下面这个数据转为 Promise 对象 const obj = { name: 'liang' } ``` 结合目前我们学习到的知识,你应该会这样做: ```javascript function foo() { const obj = { name: 'liang' } return new Promise((resolve, reject) => { resolve(obj) }); } foo().then(res => { console.log(res); }) ``` 这种写法能实现,但是用起来有点麻烦,还要使用 new Promise,那么有没有更方便的方法呢 ?当然有的 ```javascript function foo() { const obj = { name: 'liang' } return new Promise((resolve, reject) => { resolve(obj) }); } foo().then(res => { console.log(res); }) ``` 直接使用 Promise 类的方法,将数据转为 Promise 对象。其参数的用法(还是这三种情况): + 参数是一个普通的值或对象 + 参数是一个 Promise 对象 + 参数是一个包含 then 方法的对象 ```javascript const obj = { name: 'liang' } Promise.resolve(obj).then(res => { console.log(res); }) ``` #### 9. Promise 类的 reject 方法 --- reject 方法的使用也很简单,下面两段代码,作用是一样的 ```javascript function foo() { return new Promise((resolve, reject) => { reject('reject msg') }); } Promise.reject('reject msg') ``` 注意:reject 无论传入什么值都是一样的,不像 resolve 一样有三种情况 ```javascript // 传入一个普通值或对象(可以的) Promise.reject('reject msg') // 传入一个 Promise(不会因为这个 Promise 状态改变而发生什么) Promise.reject(new Promise(resolve => resolve())).catch(err => { console.log(err); }) // 传入一个包含 then 方法的对象(不会执行这个 then 方法) Promise.reject({ then(resolve, reject) { reject('reject msg') } }).catch(err => { console.log(err); }) ``` #### 10. Promise 类的 all 方法 --- 需求:所有的 Promise 都变成 fulfilled 的时候,再拿到结果 ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) ``` Promise.all 的参数是一个数组,存放多个 Promise 对象,等待数组中所有 Promise 变为 fulfilled,才会调用 resolve() ```javascript // 如果传入一个非 Promise 对象的数据,自动将其处理为 Promise 对象 // 比如: 'abc',会被处理为 Promise.resolve('abc') Promise.all([p1, p2, p3, 'abc']).then(res => { console.log(res) // [111, 222, 333, 'abc'] }) ``` 只要数组中有一个 Promise 的状态是 rejected,那么 Promise.all 的状态就会变为 rejected ```javascript Promise.all([p1, p2, p3, new Promise((_, reject) => reject('reject message'))]).then(res => { // ... }).catch(err => { console.log(err) // reject message }) ``` #### 11. Promise 类的 allSettled 方法 --- `Promise.all()`方法有个缺陷:当其中有一个 Promise 变成 reject 时,它就会变成对应的 reject 状态 + 那么,对于其中 resolved 的,我们是获取不到对应的结果的 在 ES11(ES2020)中,添加了新的 API:`Promise.allSettled()`: + 该方法会在所有的 Promise 都有结果时,变为 fulfilled 状态。无论是 fulfilled 还是 rejected 都可以,只要都有结果 + 并且这个 Promise 的结果一定是 fulfilled,不会是 rejected,所以无需写 catch 方法处理 rejected 的情况 ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((_, reject) => reject('reject message')) Promise.allSettled([p1, p2, p3]).then(res => { console.log(res) // [ // { "status": "fulfilled", "value": 111 }, // { "status": "fulfilled", "value": 222 }, // { "status": "rejected", "reason": "reject message" } // ] }) ``` #### 12. Promise 类的 race 方法 --- `Promise.race` 用于处理多个异步操作的竞争关系,特点:胜者为王。race:竞赛、赛跑 它的核心行为是:传入的多个 Promise 任意一个率先完成(无论成功还是失败),就会有立即采用该 Promise 的结果 ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) Promise.race([p1, p2]).then(res => { console.log(res) // 111 }).catch(err => { console.log(err); }) ``` #### 13. Promise 类的 any 方法 --- `Promise.any()` 方法是 ES12 中新增的方法,它会等到一个 fulfilled 状态,才会决定新 Promise 的状态 ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) Promise.any([p1, p2]).then(res => { console.log(res) // 222 }).catch(err => { console.log(err) }) ``` 如果所有的 Promise 都是 reject 的,那么也会等到所有的 Promise 都变成 rejected 状态,然后抛出 `AggregateError ` ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(222) }, 2000) }) Promise.any([p1, p2]).then(res => { console.log(res) }).catch(err => { console.log(err) // AggregateError: All promises were rejected console.log(err.errors) // [111, 222] 这是调用 reject 时的参数 }) ```