Promise 为什么会出现

任何技术的出现都是来解决一些痛点的,promise 也是不例外。
当初处理一些异步任务的时候,比如网络请求。我们会封装一个请求函数,或者使用其他人封装好的请求函数。例如这样:

  1. function requestFn() {
  2. // setTimeout 代表网络请求
  3. setTimeout(() => {
  4. }, 3000);
  5. }

但很快发现一个问题,这个请求的是结果是怎么样,到底有没有请求到数据呢?异步里面 return 的值 request 函数是拿不到的,所以我们也不知道请求是否成功或失败。
这个时候,大家就想到一个办法,既然 return 不了,那就让 request 请求成功时执行一个函数,失败时执行另一个函数。这样我们调用 request 的时候,只要我只要看哪个函数执行了,就知道请求是成功还是失败。

  1. function requestFn(fn1, fn2) {
  2. // setTimeout 代表网络请求
  3. setTimeout(() => {
  4. if(请求成功) {
  5. fn1()
  6. } else {
  7. fn2()
  8. }
  9. }, 3000);
  10. }
  11. requestFn(()=>{
  12. console.log('success');
  13. }, ()=> {
  14. console.log('failure');
  15. })

这样的设计虽然解决了难以得到异步请求结果的问题,但是大家又发现了其他问题。
比如 request 函数是别人封装的,那我们怎么知道 fn1 是请求成功会执行的函数,还是请求失败会执行的函数?这时我们要么是看它的源码,要么看它的文档,这就带来了很大的沟通问题。
还有假如异步请求中还有异步请求,那我们调用的时候就得回调里面嵌套回调,陷入回调地狱。可读性也会变差很多。
所以这种方案并不完美。

什么是 Promise

为了解决这个问题,大家就设计出了 Promise,它是一种约定,一种规范。比如大家一起说好,第一个参数函数 fn1,它就是请求成功会调用的函数,fn2 就是失败才执行的函数,大家一起固定这个顺序。这是一种约定。这个约定的实现就是 Promise。

Promise 的基本使用

Promise 是一个类,当我们需要给予调用者一个约定,就只要给调用者一个 Promise 的实例对象;
在实例化这个 promise 对象的时候,会初始化一个回调函数(说明这个回调函数定义在 Promise 类的 constructor 中),这个函数中就可以执行异步请求,这个函数我们称为 executor 执行器。
并且执行器中可以接收两个参数,这两个参数也是函数,第一个参数固定了就是在异步请求执行成功的时候才会执行,所以叫 resolve 解决;第二参数函数固定请求失败时执行,所以叫 reject 拒绝。resolve 和 reject 都是 promise 内部实现的。

  1. function requestFn() {
  2. return new Promise((resolve, reject) => {
  3. // 异步请求
  4. setTimeout(() => {
  5. if (true) { // 请求成功
  6. resolve(value) // 成功后传递的数据
  7. } else { // 请求失败
  8. reject(err) // 失败后的错误信息
  9. }
  10. }, 3000)
  11. }
  12. // 异步请求是写在 promise 里面的,所以直接拿 promise 对象就行,不用再让 requestFn 函数包裹
  13. const promise = new Promise((resolve, reject) => {
  14. // 异步请求
  15. setTimeout(() => {
  16. if (true) { // 请求成功
  17. resolve(value)
  18. } else { // 请求失败
  19. reject(err)
  20. }
  21. }, 3000)
  22. })

promise 规范了定义异步请求的部分,也规范了怎么使用这个异步请求。
现在这个异步请求相当于就是一个 promise 对象,我们调用时,就是拿到 promise 对象,其中有个实例方法then(),它可以接收一个参数函数,这个函数会传给 resolve,也就是请求成功后会执行这个函数;还有个实例方法catch(),它接收的参数函数就是被传入 reject ,也就是请求失败后会执行这个函数。

有个问题 promise 是什么时候将这两个参数从 then 和 catch 中传到 resolve 和 reject 中的?在初始化 promise 对象时,它不光接收回调函数,也就是 executor,还会立即执行 executor,是这个时候传过去的。

那我们现在可能会有个疑问?
executor 立即执行,那其中的异步请求也会立即开始执行吗?如果会执行,假设请求成功,会调用 resolve 方法,如果这个时候我没有写 then 函数,resolve 无法接收到 then 中的参数函数,那 resolve 会执行吗?还是说它会挂起,等待 then 中参数函数的传入?

首先要明白一点,实例化 promise 对象的时候就是在发送异步请求,也就是定义请求和调用请求没有明显的时间差,几乎是同时进行的。
new 实例化 promise 时,executor 开始执行,异步请求也会开始执行,这个时候如果没有 then 方法,请求成功调用到 resolve ,就会报错:ReferenceError: resolve is not defined
所以一定要写好怎么处理结果的函数,才能去实例化 promise 发送请求。

这样 promise 就规范了怎么定义异步请求,也规范了怎么调用异步请求。大家就少了很多沟通成本,而且通过 promise 实例方法的包装,出现异步请求套异步请求,调用就从 回调套回调,变成了实例方法的链式调用。

  1. const promise = new Promise((resolve, reject) => {
  2. // 异步请求
  3. setTimeout(() => {
  4. if (true) {
  5. // 请求成功
  6. resolve('hhh')
  7. } else {
  8. // 请求失败
  9. reject('kule')
  10. }
  11. }, 3000)
  12. })
  13. promise.then(value => {
  14. console.log(value + 'success')
  15. }).catch(err => {
  16. console.log(err + 'failure');
  17. })

promise 的三种状态

在创建 Promise 时需要传入的 executor,会被立即执行,并且传入 resolve 和 reject,它们的执行会改变 promise 对象的状态。
executor 只能调用一个 resolve 或一个 reject,一旦状态被确定下来,Promise的状态会被 锁死,该Promise的状态是不可更改的。在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成 兑现(fulfilled);在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);

由 new Promise 构造器返回的 promise 对象具有以下内部属性:

  • state —— 最初是 “pending”,然后在 resolve 被调用时变为 “fulfilled”,或者在 reject 被调用时变为 “rejected”。
  • result —— 最初是 undefined,然后在 resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。

image.png
为什么需要属性来标注三个状态,是为了执行顺序设计的吗?状态是 resolve 执行完变还是执行时就变?

resolve 函数的参数

resolve(value) 执行后会将带有结果的 value 返回,并且 resolve/reject 都只需要一个参数(或不包含任何参数),并且将忽略额外的参数。
但是 resolve 接收的不同参数对 promise 的状态也会造成不同影响。

  1. /**
  2. * resolve(参数)
  3. * 1> 普通的值或者对象 会作为then回调的参数,promise 状态:pending -> fulfilled
  4. * 2> 传入一个Promise
  5. * 那么当前的Promise的状态会由传入的Promise来决定
  6. * 相当于状态进行了移交
  7. * 3> 传入一个对象, 并且这个对象有实现then方法(并且这个对象是实现了thenable接口)
  8. * 那么也会执行该then方法, 并且由该then方法来决定 promise 后续状态
  9. */
  10. // 1. 普通值或对象
  11. new Promise((resolve, reject) => {
  12. resolve('hhh') // pedding -> fulfilled
  13. }).then(value => {
  14. console.log(value); // hhh
  15. }, err => {
  16. console.log(err);
  17. })
  18. // 2.传入 Promise 对象
  19. const newPromise = new Promise((resolve, reject) => {
  20. reject("err message") // 状态转移到这,这里是 reject,状态:pedding -> rejected
  21. })
  22. new Promise((resolve, reject) => {
  23. resolve(newPromise) // 执行了 resolve 按理会打印 res,但参数是 promise 对象
  24. }).then(res => {
  25. console.log("res:", res)
  26. }, err => {
  27. console.log("err:", err) // err: err message
  28. })
  29. // 3.传入一个对象, 这个对象有then方法
  30. new Promise((resolve, reject) => {
  31. const obj = {
  32. then: function(resolve, reject) {
  33. // resolve("resolve message")
  34. reject("reject message")
  35. }
  36. }
  37. resolve(obj) // 对象 then 方法执行的是 reject,状态:pedding -> rejected
  38. }).then(res => {
  39. console.log("res:", res)
  40. }, err => {
  41. console.log("err:", err) // err: reject message
  42. })

then 方法

接受两个参数函数

then 方法是 promise 实例化对象的方法。有两个参数,第一个参数函数,resolve 执行时会执行;第二个参数 reject 时会执行,也就相当于 catch 方法。
所以 then 方法可以模拟 catch 方法:then(null, (err)=>{}),其实 catch 方法是这种写法的语法糖,应该说 catch 模拟了 then 才对。

  1. const promise = new Promise((resoleve,reject) => {
  2. reject('failure')
  3. })
  4. promise.then(null, err => {
  5. console.log(err);
  6. })
  7. // failure

多次调用

  1. // 同一个 Promise 可以多次调用 then 方法
  2. // 当我们的 resolve 方法被回调时, 多个 then 方法传入的回调函数都会被执行
  3. const promise = new Promise((resolve, reject) => {
  4. resolve('success')
  5. })
  6. promise.then(res => {
  7. console.log("res1:", res)
  8. })
  9. promise.then(res => {
  10. console.log("res2:", res)
  11. })
  12. promise.then(res => {
  13. console.log("res3:", res)
  14. })
  15. // res1: success
  16. // res2: success
  17. // res3: success

返回值

then 方法本身也是有返回值的, 它的返回值是一个新的 Promise 对象。then 方法传入的 “回调函数: 也可以有返回值,并且回调函数所返回的值会被作为 then 所返回的新 Promise 对象中 resolve 方法的参数。

  1. const promise = new Promise((resolve, reject) => {
  2. resolve('success')
  3. })
  4. promise.then(res => {
  5. console.log("res:", res)
  6. return 'hhh'
  7. // 相当于把这个作为整个 then 函数的返回值
  8. // new Promise(resolve => {
  9. // resolve('hhh')
  10. // })
  11. }).then((res) => {
  12. console.log("新 promise 的异步结果:" + res );
  13. })
  14. // res: success
  15. // 新 promise 的异步结果:hhh

then 回调函数中的返回值会被当 resolve 的参数,上面刚讲了 resolve 的三种参数对 promise 对象的影响,说明 回调函数的返回值也有这三种类型,并会对 then 函数整体返回的 promise 对象造成一样的影响。
尤其是 then 函数的回调函数的返回值是一个 promise 对象时,这种情况很多。因为 promise 对象做了 then 整体返回 promise 对象 resolve 的参数,根据前文可知,当前 then 方法链式调用的下一个 then 方法,是执行成功的参数回调函数还是失败的参数回调函数,由当前 then 方法回调函数中返回的 promise 对象请求成功或失败决定。
换句话说:当前 then 方法中回调函数返回的 promise 对象的请求结果会作为整个 then 方法返回的 promise 对象的请求结果。

  1. const promise1 = new Promise(resolve => {
  2. resolve('success')
  3. })
  4. const promise2 = new Promise(resolve => {
  5. resolve('success')
  6. })
  7. const promise3 = new Promise(resolve => {
  8. resolve('success')
  9. })
  10. // 1> 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined), 那么这个普通的值被作为一个新的Promise的resolve值
  11. promise1.then(res => {
  12. return "aaaaaa"
  13. }).then(res => {
  14. console.log("res:", res)
  15. return "bbbbbb"
  16. })
  17. // 2> 如果我们返回的是一个Promise
  18. promise2.then(res => { // 返回的 promise 请求结果会作为这个 then 返回的 promise 的结果
  19. return new Promise((resolve, reject) => {
  20. setTimeout(() => {
  21. resolve(111111)
  22. }, 3000)
  23. })
  24. }).then(res => {
  25. console.log("res:", res)
  26. })
  27. // 3> 如果返回的是一个对象, 并且该对象实现了thenable
  28. promise3.then(res => {
  29. return {
  30. then: function(resolve, reject) {
  31. resolve(222222)
  32. }
  33. }
  34. }).then(res => {
  35. console.log("res:", res)
  36. })
  37. // res: aaaaaa
  38. // res: 222222
  39. // res: 111111

executor 中抛出异常

当没有执行到 resolve 或 reject 中就抛出了异常,那 promise 状态也是 rejected。并会执行 catch 方法。

  1. const promise = new Promise((resoleve,reject) => {
  2. throw new Error('出现错误')
  3. })
  4. promise.then(null, err => {
  5. console.log(err);
  6. })
  7. // Error: 出现错误

catch 方法

catch 方法是then(null, ()=>{})的语法糖。

catch 方法特殊的链式调用

  1. const promise = new Promise((resoleve,reject) => {
  2. resolve('success')
  3. })
  4. promise.then(res => {
  5. console.log(res);
  6. }).catch(err => { // then 会返回一个新的 promise,那这是新的 promise 在调用 catch 吗?
  7. console.log(err);
  8. })

看起来好像是 then 返回的新 promise 对象在调用 catch,其实不是的。这样的链式调用,catch 会优先和 then 处于同一级结构,它会优先捕获 then 方法的 promise 请求失败的结果。如果老的 promise 请求成功,则会再来看 then 返回的新 promise 是否请求失败。

  1. const promise1 = new Promise((resoleve,reject) => {
  2. reject('先调用 then 方法的 promise 请求 failure')
  3. })
  4. const promise2 = new Promise((resoleve,reject) => {
  5. resoleve('success')
  6. })
  7. promise1.then(res => {
  8. console.log('res:' + res);
  9. return new Promise((resoleve, reject) => {
  10. reject('新的 Promise 也请求错误')
  11. })
  12. }).catch(err => { // 先捕获老的 promise 请求结果
  13. console.log('err:' + err);
  14. })
  15. // err:先调用 then 方法的 promise 请求 failure
  16. promise2.then(res => {
  17. return new Promise((resoleve, reject) => {
  18. reject('返回的新 promise 请求 failure')
  19. }).catch(err => { // 老 promise 请求成功,就捕获了新的 promise 请求失败的结果
  20. console.log(err);
  21. })
  22. })
  23. // 返回的新 promise 请求 failure

单独使用 catch 方法

单独使用 catch 方法,看起来和 then 好像是一对。其实它们算是单独使用,因为 catch 没有和 then 建立联系。
第一个promise 只有 then ,缺少了捕获失败请求的回调,所以如果请求失败就会报错;第二个 promise 只有 catch,缺少了处理成功的回调,同理如果请求成功,也会报错。

  1. const promise = new Promise((resolve, reject) => {
  2. reject('failure')
  3. })
  4. // 看起来是 then 和 catch 是一对,其实是独立调用
  5. promise.then(res => {
  6. console.log(res)
  7. })
  8. promise.catch(err => {
  9. console.log(err)
  10. })

怎么 then 和 catch 才是一对呢?

  • 不用 catch,then 接收两个参数函数
  • then 后面链式调用 catch ```javascript // 方式一:其实 then 接收两个参数才是标准格式 promise.then( res => { console.log(res) }, err => { console.log(err) } )

// 方式二 promise .then(res => { console.log(res) }) .catch(err => { console.log(err) })

  1. <a name="iU6pq"></a>
  2. ## catch 返回值
  3. catch 方法返回值和 then 的返回值一模一样,也是一个新 promise。并且回调函数的返回值也会被给到新 promise 的 resolve 的 value。<br />**注意:和 then 的回调函数一样,返回值也是给 resolve,不是 reject**
  4. ```javascript
  5. const promise = new Promise((resolve, reject) => {
  6. reject('failure')
  7. })
  8. promise
  9. .then(res => {
  10. console.log(res)
  11. })
  12. .catch(err => {
  13. return 'hhh'
  14. })
  15. .then(res => {
  16. console.log('then 执行了' + res) // 因为给的是 resolve,所以接下来还是执行 then
  17. }).catch(err => {
  18. console.log('catch 执行了' + err)
  19. })
  20. // then 执行了hhh

finally 方法

finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会
被执行的代码。
finally方法是不接收参数的。

  1. const promise = new Promise((resolve, reject) => {
  2. reject('reject message')
  3. })
  4. promise
  5. .then(res => {
  6. console.log('res:', res)
  7. })
  8. .catch(err => {
  9. console.log('err:', err)
  10. })
  11. .finally(() => {
  12. console.log('finally code execute')
  13. })
  14. // err: reject message
  15. // finally code execute

Promise 的静态方法

Promise.resolve

有时候我们已经有一个现成的请求结果数据了,希望将其转成 Promise 来使用,这个时候我们可以使用 Promise.resolve方法来完成。

  1. // 转成Promise对象
  2. function foo() {
  3. const obj = { name: "why" }
  4. return new Promise((resolve) => {
  5. resolve(obj)
  6. })
  7. }
  8. foo().then(res => {
  9. console.log("res:", res)
  10. })
  1. // 类方法Promise.resolve
  2. const promise1 = Promise.resolve({ name: "why" })
  3. // 其实就相当于下面这样写
  4. const promise2 = new Promise((resolve, reject) => {
  5. resolve({ name: "why" })
  6. })

同样的类方法 resolve 和实例化方法 resolve 一样可以接收三种参数,产生不一样的结果。
情况一:参数是一个普通的值或者对象
情况二:参数本身是Promise
情况三:参数是一个thenable

Promise.reject

reject方法类似于resolve方法,只是会将 Promise 对象的状态设置为 reject 状态。Promise.reject 的用法相当于new Promise,只是会调用reject:
Promise.reject 传入的参数无论是啥类型,都会直接作为 reject 状态的参数传递到 catch 的。

  1. // const promise = Promise.reject("rejected message")
  2. // 相当于
  3. // const promise2 = new Promsie((resolve, reject) => {
  4. // reject("rejected message")
  5. // })
  6. // 注意: 无论传入什么值都是一样的,直接传给 catch
  7. const promise = Promise.reject(new Promise(() => {}))
  8. promise.then(res => {
  9. console.log("res:", res)
  10. }).catch(err => {
  11. console.log("err:", err)
  12. })
  13. // err: Promise { <pending> }

all 方法

all 方法,更具体一点应该说是 all fulfilled 方法。它可以以数组的形式接收多个 promise 对象,如果所有 promise 请求成功,也就是状态为 fulfilled,all 方法就会以数组的形式返回所有 promise 对象的请求结果。当有一个 Promise 状态为 reject 时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数。

相当于 all 方法中间会返回一个新 promise,参数数组的 promise 全是 fulfilled,新 promise 就也是 fulfilled,然后去掉用 then 方法,最终返回所有 promise 对象的请求结果。

  1. // 创建多个Promise
  2. const p1 = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve(11111)
  5. }, 1000);
  6. })
  7. const p2 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve(22222)
  10. }, 2000);
  11. })
  12. // p3 请求失败
  13. const p3 = new Promise((resolve, reject) => {
  14. setTimeout(() => {
  15. reject('failure')
  16. }, 3000);
  17. })
  18. // 需求: 所有的Promise都变成fulfilled时, 再拿到结果
  19. Promise.all([p1, p2, 'aaaa']).then(res => {
  20. console.log(res)
  21. })
  22. // [ 11111, 22222, 'aaaa' ]
  23. // 意外: 在拿到所有结果之前, 有一个promise变成了rejected
  24. // 那么整个promise是rejected,就只会捕获请求失败的信息
  25. Promise.all([p1, p2, p3, "bbbb"]).then(res => {
  26. console.log(res)
  27. }).catch(err => {
  28. console.log("err:", err)
  29. })
  30. // err: failure

allSettled 方法

allSettled 方法和 all 方法差不多,就是没有 all 那么严苛。它只要 promise 有确切的结果(Settled),无论是请求失败还是成功,它都把结果以数组的形式返回。而不是只要出现请求失败,就只返回请求失败的信息。
数组元素就是 promise settled 后的对象。

  1. // 创建多个Promise
  2. const p1 = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve(11111)
  5. }, 1000);
  6. })
  7. const p2 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve(22222)
  10. }, 2000);
  11. })
  12. // 请求失败
  13. const p3 = new Promise((resolve, reject) => {
  14. setTimeout(() => {
  15. reject('failure')
  16. }, 3000);
  17. })
  18. // allSettled
  19. Promise.allSettled([p1, p2, p3]).then(res => {
  20. console.log(res)
  21. }).catch(err => {
  22. console.log(err)
  23. })
  24. // [
  25. // { status: 'fulfilled', value: 11111 },
  26. // { status: 'fulfilled', value: 22222 },
  27. // { status: 'rejected', reason: 'failure' }
  28. // ]

race 方法

race 和 all,allSettled 的基本使用差不多。对于多个 promise,race 方法,会看哪个 promise 最先请求成功,然后拿到请求成功的数据。如果这些 promise 还没有哪个请求成功,就已经有 promise 请求失败了,那 race 方法就会去执行 catch。

  1. // 创建多个Promise
  2. const p1 = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve(11111)
  5. }, 3000);
  6. })
  7. const p2 = new Promise((resolve, reject) => {
  8. setTimeout(() => {
  9. resolve(22222) // 这个请求最先成功
  10. }, 1000);
  11. })
  12. const p3 = new Promise((resolve, reject) => {
  13. setTimeout(() => {
  14. reject('failure') // 还没有请求成功的promise,就有请求失败的promise
  15. }, 100);
  16. })
  17. // race: 竞技/竞赛
  18. // 只要有一个Promise变成 fulfilled 状态, 那么就结束
  19. Promise.race([p1, p2]).then(res => {
  20. console.log("res:", res)
  21. }).catch(err => {
  22. console.log("err:", err)
  23. })
  24. // res: 22222
  25. // 请求失败的最先出结果
  26. Promise.race([p1, p2, p3]).then(res => {
  27. console.log("res:", res)
  28. }).catch(err => {
  29. console.log("err:", err)
  30. })
  31. // err: failure

any 方法

any方法是ES12中新增的方法,和 race 方法是类似的:和 race 不同在于,any 方法一定会等到请求成功的 promise 出现。
比如多个 promise,其他 promise 请求都失败了,最后一个 promise 请求成功了,则 any 方法就会返回这个成功的 promise 的结果。如果最后所有 promise 请求的结果都是失败的。那 any 方法就会去执行 catch,并且报一个 AggregateError 的错误。

  1. // 创建多个Promise
  2. const p1 = new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. // resolve(11111)
  5. reject(1111)
  6. }, 1000);
  7. })
  8. const p2 = new Promise((resolve, reject) => {
  9. setTimeout(() => {
  10. reject(22222)
  11. }, 500);
  12. })
  13. const p3 = new Promise((resolve, reject) => {
  14. setTimeout(() => {
  15. resolve(33333)
  16. }, 3000);
  17. })
  18. // any方法: p1 p2 都失败了,执行时间最长的 p3 成功了,any 方法就会返回 p3 的结果
  19. Promise.any([p1, p2, p3]).then(res => {
  20. console.log("res:", res)
  21. }).catch(err => {
  22. console.log("err:", err.errors)
  23. })
  24. // res: 33333
  25. // 如果所有 promise 都是失败的,any 等完所有 promise 也没等到请求成功的,就会报错
  26. Promise.any([p1, p2]).then(res => {
  27. console.log("res:", res)
  28. }).catch(err => {
  29. console.log("err:", err)
  30. })
  31. // err: AggregateError: All promises were rejected