Promise的含义

Promise是异步编程的一种解决方案,比传统的解决方案回调函数和事件更合理更强大。
从语法上理解是一个构造函数可以进行对象的实例化,该对象可以获取异步操作的消息。一个Promise对象代表着一个还未完成,但预期将来会完成的操作。

  • Promise支持链式调用,可以解决回调地狱问题(回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件,回调地狱不易阅读,不易异常处理)

    Promise 对象有两个特点:

  1. 对象的状态不受外界影响。 Promise 对象代表一个异步操作,有三种状态: pending 、 fulfilled 和 rejected 。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从 pending -> fulfilled 和 pending -> rejected 。只要这两种情况发生,状态就凝固了,不会再改变。

image.png

基本使用

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
下面代码创建一个Promise实例。

  1. const promise = new Promise(function (resolve, reject) {
  2. // ... some code
  3. if(/* */){
  4. resolve(value)
  5. }else{
  6. reject(error)
  7. }
  8. })

Promise构造函数接受一个函数作为对象,该函数有两个参数分别是 resolve 和 reject 。它们是两个函数,由JavaScript引擎提供,不用自己部署。

resolve() 作用是:将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
reject() 作用是:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise 实例生成后,可以用 then() 方法分别指定 resolved 状态和 rejected 状态的回调函数。

  1. promise.then(function(value){
  2. // success
  3. },function(error){
  4. // failure
  5. })

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态为 resolved 时调用,第二个回调函数是Promise对象状态为 rejected 时调用。这两函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。

Promise.prototyoe.then()

Promise 实例具有 then 方法。作用是为Promise实例添加状态改变时的回调函数,具体如下所述。
then() 方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then() 方法返回一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
返回值:
image.png

Promise.prototype.catch()

方法是 .then(null,rejection) 或者 .then(undefined,rejection) 的别名,用于指定发生错误时的回调函数。catch为then的语法糖

  • 下面是一个Promise对象的简单例子(来自ECMAScript6入门) ```javascript function timeout(ms) { return new Promise((resolve, reject) => {
    1. setTimeout(resolve, ms, 'done'); // 计时器三个参数是作为第一个函数参数的实参
    }); }

timeout(2000).then((value) => { console.log(value); });

  1. 解析:timeout函数返回一个Promise实例,表示一段时间后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为了resolved,就会触发then方法绑定的回调函数。<br />无论then还是catch都会返回一个新的Promise对象,
  2. - **Promise 新建后就会立即执行。**
  3. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21370038/1621859291256-baca4812-5f32-4a90-a0af-ada55437fa5a.png#height=164&id=az0Ow&margin=%5Bobject%20Object%5D&name=image.png&originHeight=328&originWidth=690&originalType=binary&ratio=1&size=82049&status=done&style=none&width=345)
  4. <a name="xy9mT"></a>
  5. #### Promise封装 - AJAX请求
  6. - 封装一个函数 sendAJAX发送GET AJAX请求 参数URL 返回结果是Promise对象
  7. ```javascript
  8. function sendAJAX(url) {
  9. return P = new Promise((resolve, reject) => {
  10. // 1.创建ajax对象
  11. const xhr = new XMLHttpRequest()
  12. // 设置响应体数据格式
  13. xhr.responseType = 'json'
  14. // 2.建立连接
  15. xhr.open('GET', url)
  16. // 3.发送请求
  17. xhr.send()
  18. // 4.处理响应结果
  19. xhr.onreadystatechange = function () {
  20. // 判断ajax的状态码,4为浏览器解析响应体完毕,可以正常使用,对象读取响应结束
  21. if (xhr.readyState === 4) {
  22. // 判断响应状态码 200-299
  23. if (xhr.status >= 200 && xhr.status < 300) {
  24. // 控制台输出响应体
  25. resolve(xhr.response)
  26. } else {
  27. // 控制台输出响应状态码
  28. reject(xhr.status)
  29. }
  30. }
  31. }
  32. })
  33. }
  34. sendAJAX('https://api.apiopen.top/getJoke')
  35. .then(value => { console.log(value) }, error => { console.warn(error) })

Promise 基本工作流程:

image.png

Promise函数对象的API

以下方法属于Promise这个函数对象,非实例对象。

Promise.resolve()

作用:将现有对象转为Promise对象。

  1. const P1 = Promise.resolve('foo')
  2. // 等价于
  3. const P2 = new Promise(resolve => resolve('foo'))
  4. console.log(P1 == P2) // false

Promise.resolve()方法的参数分成四种情况:

  1. 参数是一个Promise实例
  2. 参数是一个thenable对象
  3. 参数不是具有then()方法的对象,或根本不是对象
  4. 不带有任何参数

总结
传入参数为 非Promise类型的对象,则返回的结果为成功promise对象
传入参数为 Promise对象,则参数的结果决定了resolve的结果

Promise.reject()

Promise.reject(reason) 方法返回一个新的 Promise 实例,该实例的状态为 rejected 。

Promise.all()

方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

  1. const P = Promise.all([p1, p2, p3]);

Promise.all() 方法接受一个数组作为参数,数组每一项都是Promise实例,如果不是,就会先调用上面讲到的Promise.resolve()方法,将参数转为Promise实例,再进一步处理。
P的状态由p1、p2、p3决定,分成两种情况:

  1. p1、p2、p3的状态都为 fulfilled ,P的状态才会变成 fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给P的回调函数
  2. p1、p2、p3中有一个被 rejected ,P的状态就会变成rejected,此时第一个被reject的实例的返回值,会传递给P的回调函数。 ```javascript const p1 = new Promise((resolve, reject) => { // setTimeout(resolve, 2000, ‘p1成功’) resolve(‘p1成功’) }) const p2=new Promise((resolve,reject)=>{ // setTimeout(resolve, 2000, ‘p2成功’) setTimeout(reject, 2000, ‘p2失败’) }) const p3 = Promise.resolve(‘P3成功’)

const P = Promise.all([p1, p2, p3]) .then(result => { console.log(‘result’,result) console.log(‘成功’,P) },result=>{ console.log(‘result’,result) console.log(‘失败’,P) }) console.log(P)

  1. 上面代码中,promise是包含3Promise实例的数组,只有这个3个实例的状态都变成 fulfilled ,或者其中一个变为 rejected ,才会调用 Promise.all 方法后面的回调函数。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21370038/1621926823557-5d3ff760-176a-4889-b3dd-41ff7d184e5a.png#height=130&id=uYpWo&margin=%5Bobject%20Object%5D&name=image.png&originHeight=260&originWidth=982&originalType=binary&ratio=1&size=177879&status=done&style=none&width=491)<br />⚠️**注意**,如果作为参数的Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。(then也是同理)
  2. ```javascript
  3. const p2 = new Promise((resolve, reject) => {
  4. // setTimeout(resolve, 2000, 'p2成功')
  5. setTimeout(reject, 2000, 'p2失败')
  6. })
  7. // .then(resolve => { console.log(resolve) })
  8. .catch(error => { console.log('catch', error) })

上述代码中,p2会rejected,但是p2有自己的catch方法,该方法返回的是一个新的Promise实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里的3个实例都会resolved。因此有了如下输出。
image.png

Promise.race()

Promise.race() 方法同样是将多个Promise实例,包装成一个新的Promise实例。

  1. const P = Promise.race([p1, p2, p3]);

上述代码中,只要p1、p2、p3之中有一个实例率先改变状态,P的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给P的回调函数。

返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态

Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。

  • 案例:如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve ```javascript const p = Promise.race([ fetch(‘https://api.apiopen.top/getJoke‘), new Promise(function (resolve, reject) {
    1. setTimeout(() => reject(new Error('request timeout')), 5000)
    }) ]);

p .then(console.log) .catch(console.error);

  1. 上述代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法的指定的回调函数。
  2. <a name="DRiAF"></a>
  3. ## 硬核 - Promise封装
  4. <a name="ZNSMN"></a>
  5. ### promise几个关键问题
  6. <a name="L8XkI"></a>
  7. #### 1. 如何改变Promise的状态?
  8. resolve()、reject()、抛出异常。
  9. ```javascript
  10. const P = new Promise((resolve,reject)=>{
  11. // 1. resolve('ok'); // pending => fulfilled
  12. // 2. reject('error'); // pending => rejected
  13. // 3. throw '出现了问题'; // pending => rejected
  14. })
  15. console.log(P)

2. 一个Promise指定多个成功/失败回调函数,都会调用吗?

当Promise改变为对应状态时都会调用

  1. const p = new Promise((resolve, reject) => {
  2. resolve('成功啦');
  3. resolve('成功');
  4. })
  5. // 指定回调 - 1
  6. p.then(value => {
  7. console.log('第一个then',value);
  8. })
  9. // 指定回调 - 2
  10. p.then(value => {
  11. console.log('第二个then',value);
  12. })

3. 改变Promise状态和指定回调函数谁先谁后?

当改变状态的函数是异步任务时,then方法的回调函数先执行。

  • 如何先改变状态再指定回调?

(1)在执行器中直接调用resolve()/reject()
(2)延迟更长时间再调用then()

  • 什么时候才能得到数据?

(1)如果先执行指定回调,那当状态发生改变时,回调函数就会调用,得到数据
(2)如果先执行改变状态,那当指定回调时,回调函数就会调用,得到数据

4. Promise.then()返回的新Promise的结果状态由什么决定?

  1. then()指定的回调函数执行的结果决定 <br />(1)如果抛出异常,新的Promise变为rejectedreason为抛出的异常<br />(2)如果返回的是非Promise的任意值,新Promise变为resolvedvalue为返回的值 <br />没有返回值,即是返回undefined,新Promise变为resolvedvalueundefined<br />(3)如果返回的是另一个新Promise,此Promise的结果就会成为新Promise的结果
  1. const P=new Promise((resolve,reject)=>{
  2. resolve('ok')
  3. })
  4. const result =P.then(value=>{
  5. // throw '出了问题' // rejected / 出了问题
  6. // return '321' // fulfilled / '321'
  7. return new Promise((resolve,reject)=>{
  8. resolve('OKK') // fulfilled / OKK
  9. // reject('ONN') // rejected / ONN
  10. })
  11. },error=>{
  12. console.log(error)
  13. })
  14. console.log('result',result)

5. Promise如何串联多个操作任务?

因为Promise的then()返回一个新的Promise,通过then的链式调用串联多个同步/异步任务

6. Promise的异常穿透

当使用Promise的then链式调用时,可以在最后指定失败的回调,前面任何操作出了异常,都会传到最后失败的回调中处理。

  1. const P = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. reject('失败');
  4. }, 1000)
  5. })
  6. P.then(value => {
  7. console.log(value, 111);
  8. }).then(value => {
  9. console.log(value, 222);
  10. }).then(value => {
  11. console.log(value, 333);
  12. })
  13. .catch(error => {
  14. console.warn(error); // 失败
  15. })

7. 中断Promise链

当使用Promise的then链式调用时,在中间中断,不再调用后面的回调函数。
方法:有且只要一种,在回调函数中返回一个pendding状态的Promise对象

  1. const P = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('成功');
  4. }, 1000)
  5. })
  6. P.then(value => {
  7. console.log(value, 111);
  8. // 有且只有一个方法 ⬇️
  9. return new Promise(() => { })
  10. }).then(value => {
  11. console.log(value, 222);
  12. }).then(value => {
  13. console.log(value, 333);
  14. }).catch(error => {
  15. console.warn(error);
  16. })

∆手写promise

简易版

  1. class Promise {
  2. constructor(executor) {
  3. this.state = 'pending';
  4. this.value = undefined;
  5. this.reason = undefined;
  6. this.onResolvedCallbacks = [];
  7. this.onRejectedCallbacks = [];
  8. try { // 如果executor执行报错,直接执行reject
  9. executor(this.resolve, this.reject);
  10. } catch (err) {
  11. reject(err);
  12. }
  13. }
  14. //成功
  15. resolv(value) {
  16. // state改变,resolve调用就会失败
  17. if (this.state === 'pending') {
  18. // resolve调用后,state转化为成功态
  19. this.state = 'fulfilled';
  20. // 储存成功的值
  21. this.value = value;
  22. }
  23. };
  24. //失败
  25. reject(reason) {
  26. // state改变,reject调用就会失败
  27. if (this.state === 'pending') {
  28. // reject调用后,state转化为失败态
  29. this.state = 'rejected';
  30. // 储存失败的原因
  31. this.reason = reason;
  32. }
  33. };
  34. //then方法
  35. then(onFulfilled, onRejected) {
  36. // 声明返回的promise2
  37. let promise2 = new Promise((resolve, reject) => {
  38. if (this.state === 'fulfilled') {
  39. let x = onFulfilled(this.value);
  40. // resolvePromise函数,处理自己return的promise和默认的promise2的关系
  41. resolvePromise(promise2, x, resolve, reject);
  42. };
  43. if (this.state === 'rejected') {
  44. let x = onRejected(this.reason);
  45. resolvePromise(promise2, x, resolve, reject);
  46. };
  47. if (this.state === 'pending') {
  48. this.onResolvedCallbacks.push(() => {
  49. let x = onFulfilled(this.value);
  50. resolvePromise(promise2, x, resolve, reject);
  51. })
  52. this.onRejectedCallbacks.push(() => {
  53. let x = onRejected(this.reason);
  54. resolvePromise(promise2, x, resolve, reject);
  55. })
  56. }
  57. });
  58. // 返回promise,完成链式
  59. return promise2;
  60. }
  61. }

复杂版

  1. function MyPromise(fn) {
  2. this.PromiseState = 'pending';
  3. this.PromiseResult = undefined;
  4. // 注册该 MyPromise对象resolve任务函数
  5. this.thenCallback = undefined;
  6. // 注册该 My Promise对象reject任务函数
  7. this.catchCallback = undefined;
  8. var _this = this;
  9. var resolve = function (resolveValue) {
  10. // console.log(this) // 该this指向Window
  11. if (_this.PromiseState === 'pending') {
  12. // 状态和值变更
  13. _this.PromiseState = 'fulfilled';
  14. _this.PromiseResult = resolveValue;
  15. // 利用setTimeout模拟Promise对象的异步控制
  16. // 虽然resolve是在then函数前执行的
  17. // 但是该函数的回调一定是在Promise对象初始化完毕后执行的
  18. // 所以我们的回调执行时,thenCallback就已经初始化完毕了
  19. if (resolveValue instanceof MyPromise) {
  20. // 当传入的 resolveValue 的值的类型是 MyPromise对象本身时
  21. // 不需要使用异步控制,直接用它的then去处理下一步的流程
  22. resolveValue.then(function (res) {
  23. if (_this.thenCallback) {
  24. _this.thenCallback(res);
  25. }
  26. });
  27. } else {
  28. setTimeout(function () {
  29. if (_this.thenCallback) {
  30. _this.thenCallback(resolveValue);
  31. }
  32. });
  33. }
  34. }
  35. }
  36. var reject = function (rejectValue) {
  37. if (_this.PromiseState === 'pending') {
  38. _this.PromiseState = 'rejected';
  39. _this.PromiseResult = rejectValue;
  40. setTimeout(function () {
  41. // 当只有 catchCallback 的时候代表直接写的catch直接发流程即可
  42. if (_this.catchCallback) {
  43. _this.catchCallback(rejectValue);
  44. } else if (_this.thenCallback) {
  45. // 如果没有 catchCallback 但是存在 thenCallback 代表Promise对象直接使用了then
  46. // 所以此时应该先让then执行来进行本次函数的跳过,直接找到catch
  47. _this.thenCallback(rejectValue)
  48. } else {
  49. throw (`(in promise) / no catch found ${_this.PromiseResult}`)
  50. }
  51. })
  52. }
  53. }
  54. if (fn) {
  55. fn(resolve, reject);
  56. } else {
  57. throw ('Uncaught TypeError: Promise resolver undefined is not a functionat new Promise (<anonymous>)');
  58. }
  59. }
  60. MyPromise.prototype.then = function (callback) {
  61. var _this = this;
  62. // 实现链式调用,需要return回去一个新的Promise对象
  63. return new MyPromise(function (resolve, reject) {
  64. // 我们通常在then函数执行的时候优先在函数内部注册回调函数任务
  65. // 等待resolve执行的时候通过注册异步的任务来在该回调后捕获它
  66. _this.thenCallback = function (value) {
  67. // 由于catch的链式调用比较复杂
  68. // 所以可能在catch执行的时候会触发 thenCallback
  69. // 所以在此需要判断当前Promise对象的状态是不是 rejected(已拒绝)
  70. if (_this.PromiseState === 'rejected') {
  71. // 如果是 rejected 触发的thenCallback,直接调用下一个对象的reject
  72. reject(value);
  73. } else {
  74. var res = callback(value); // 获取then方法的返回值
  75. // 判断。如果某一次then函数返回的是一个 rejected 状态的Promise对象
  76. // 此时我们需要在这里直接注册它的catch,并且在catch内部拿到对象的结果
  77. // 然后通过下一个对象的reject链式的通知最近的catch函数执行
  78. if (res instanceof MyPromise && res.PromiseState === 'rejected') {
  79. res.catch(function (errvalue) {
  80. reject(errvalue);
  81. })
  82. } else {
  83. resolve(res);
  84. }
  85. }
  86. }
  87. })
  88. }
  89. MyPromise.prototype.catch = function (callback) {
  90. var _this = this;
  91. return new MyPromise(function (resolve, reject) {
  92. _this.catchCallback = function (value) {
  93. var res = callback(value)
  94. // 由于catch本次的对象为rejected状态,但是如果继续调用默认触发的还是then函数
  95. resolve(res);
  96. }
  97. })
  98. }
  99. MyPromise.resolve = function (value) {
  100. return new MyPromise(function (resolve, reject) {
  101. resolve(value);
  102. })
  103. }
  104. MyPromise.reject = function (value) {
  105. return new MyPromise(function (resolve, reject) {
  106. reject(value);
  107. })
  108. }
  109. MyPromise.all = function (PromiseArr) {
  110. var resArr = [];
  111. return new MyPromise(function (resolve, reject) {
  112. // PromiseAll的特点是,必须等待PromiseArr数组中所有的Promise状态都为 fulfilled 之后才会触发then
  113. PromiseArr.forEach((promiseItem, index) => {
  114. promiseItem.then(function (res) {
  115. resArr[index] = res;
  116. let success = PromiseArr.every(item => {
  117. return item.PromiseState === 'fulfilled';
  118. })
  119. if (success) {
  120. resolve(resArr);
  121. }
  122. }).catch(function (err) {
  123. reject(err);
  124. })
  125. })
  126. })
  127. }
  128. MyPromise.race = function (PromiseArr) {
  129. return new MyPromise(function (resolve, reject) {
  130. PromiseArr.forEach((promiseItem, index) => {
  131. promiseItem.then(function (res) {
  132. resolve(res);
  133. }).catch(function (err) {
  134. reject(err);
  135. })
  136. })
  137. })
  138. }

实现 Primise.all

实现 Promise.all

1)核心思路

  1. 接收一个 Promise 实例的数组或具有 Iterator 接口的对象作为参数
  2. 这个方法返回一个新的 promise 对象,
  3. 遍历传入的参数,用Promise.resolve()将参数”包一层”,使其变成一个promise对象
  4. 参数所有回调成功才是成功,返回值数组与参数顺序一致
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码
一般来说,Promise.all 用来处理多个并发请求,也是为了页面数据构造的方便,将一个页面所用到的在不同接口的数据一起请求过来,不过,如果其中一个接口失败了,多个请求也就失败了,页面可能啥也出不来,这就看当前页面的耦合程度了~

  1. function promiseAll(promises) {
  2. return new Promise(function (resolve, reject) {
  3. if (!Array.isArray(promises)) {
  4. throw new TypeError(`argument must be a array`)
  5. }
  6. var resolvedCounter = 0;
  7. var promiseNum = promises.length;
  8. var resolvedResult = [];
  9. for (let i = 0; i < promiseNum; i++) {
  10. Promise.resolve(promises[i]).then(value => {
  11. resolvedCounter++;
  12. resolvedResult[i] = value;
  13. if (resolvedCounter == promiseNum) {
  14. return resolve(resolvedResult)
  15. }
  16. }, error => {
  17. return reject(error)
  18. })
  19. }
  20. })
  21. }
  22. // test
  23. let p1 = new Promise(function (resolve, reject) {
  24. setTimeout(function () {
  25. resolve(1)
  26. }, 1000)
  27. })
  28. let p2 = new Promise(function (resolve, reject) {
  29. setTimeout(function () {
  30. resolve(2)
  31. }, 2000)
  32. })
  33. let p3 = new Promise(function (resolve, reject) {
  34. setTimeout(function () {
  35. resolve(3)
  36. }, 3000)
  37. })
  38. promiseAll([p3, p1, p2]).then(res => {
  39. console.log(res) // [3, 1, 2]
  40. })