js-2.jpg概括

  1. new Promise((resolve,reject) => {
  2. /* 业务逻辑等 */
  3. resolve()
  4. }).then(res => {}, err => {})
  1. 这里的resolve,reject可看作Promise类暴露出来的接口,用来改变promise实例的状态,改变状态后,由then中的两个回调来做处理。执行流程是

立即执行传给Promise的executor -> 同步执行then, 收集then中的回调 -> executor中触发resolve/reject -> 触发所收集的回调

  1. then中回调都是对前一个promise实例的处理,并返回一个新的promise。
  2. then的值穿透原理就是,then返回新的promise实例的时候,这个实例内的属性state会被被上一个then中回调函数的结果所改变,继而resolve/reject的时候,触发(已经收集的)下一个then中的回调。
  • Promise 解决了什么问题?

    回调地狱(then的链式调用)、 处理多个异步请求并发(promise.all)。Promise 解决的是异步编码风格的问题,增加可阅读性和可维护性

  • Promise 的业界实现都有哪些?

    bluebird、Q、ES6-Promise Promise 常用的 API 有哪些? Promise.resolve() Promise.reject() Promise.prototype.catch() Promise.prototype.finally() Promise.all() Promise.race()

  • 能不能手写一个符合 Promise/A+ 规范的 Promise?

见下文

  • Promise 在事件循环中的执行过程是怎样的?

promise.then属于微任务,在下一个事件循环前,会检查是否存在微任务,有则执行。(当前执行栈清空后,检查微任务队列,有则执行)

  • Promise 有什么缺陷,可以如何解决?

无法中断

基本实现

结合 Promise/A+ 规范,我们可以分析出 Promise 的基本特征

  • promise 有三个状态:pendingfulfilled,or rejected;「规范 Promise/A+ 2.1」
  • new promise时, 需要传递一个executor()执行器,执行器立即执行;
  • executor接受两个参数,分别是resolvereject
  • promise 的默认状态是 pending
  • promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」
  • promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」
  • promise 只能从pendingrejected, 或者从pendingfulfilled,状态一旦确认,就不会再改变;
  • promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」
  • 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promisevalue
  • 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promisereason
  • 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected
  1. // tip: class声明的类中, 默认执行严格模式,this在未明确指定的情况下,指向undefined,而不是window.
  2. // this 跟作用域无关,而跟调用执行函数时的执行上下文有关
  3. const PENDING = 'PENDING'
  4. const FULFILLED = 'FULFILLED'
  5. const REJECTED = 'REJECTED'
  6. class MyPromise {
  7. constructor (executor) {
  8. this.state = PENDING
  9. this.value = undefined // 存放成功的值
  10. this.reason = undefined // 存放失败的原因
  11. this.onResolveCallbacks = [] // 存放成功的回调
  12. this.onRejecedCallbacks = [] // 存放失败的回调
  13. let resolve = (value) => { // 箭头函数,this是外层的this
  14. this.state = FULFILLED
  15. this.value = value
  16. this.onResolveCallbacks.forEach(fn => fn()); // 在调用resolve时,将收集的回调触发一遍
  17. }
  18. let reject = (reason) => {
  19. this.state = REJECTED
  20. this.reason = reason
  21. this.onRejecedCallbacks.forEach(fn => fn()); // 在调用reject时,同理
  22. }
  23. try {
  24. // 执行传给new Promise的函数,里面的逻辑各种各样,如果代码本身出了问题,也会被try-catch捕获,进而被后续的then或者.catch捕捉到
  25. // 这就是很多代码typeError或者referenceError没有在控制台到看到的原因,报错被promise.catch了
  26. // executor里代码的逻辑各有需求,但是一旦调用了resolve、reject,就是改变state触发then中收集的回调的时候了
  27. executor(resolve, reject)
  28. } catch (error) {
  29. reject(error)
  30. }
  31. }
  32. then (onFulfilled, onRejected) {
  33. if (this.state === FULFILLED) {
  34. onFulfilled(this.value)
  35. }
  36. if (this.state === REJECTED) {
  37. onRejected(this.reson)
  38. }
  39. // 如果没有对pengding时回调的收集,那么将无法在异步调用resolve/reject时候触发onFulfilled/onRejected
  40. // 比如下面setTimeout时resolve,由于执行then的时候,state还是pending状态,那么什么都不会执行
  41. if (this.state === PENDING) {
  42. //这里是一个发布订阅模式,这里收集依赖 -> 在某个时机(调用resolve/reject时)触发通知 -> 取出依赖
  43. this.onResolveCallbacks.push(() => onFulfilled(this.value))
  44. this.onRejecedCallbacks.push(() => onRejected(this.reson))
  45. }
  46. }
  47. }
  48. const promise = new MyPromise((resolve, reject) => {
  49. setTimeout(() => {
  50. resolve('成功')
  51. }, 1000);
  52. }).then(
  53. res => console.log('res ---', res),
  54. error => console.log('error ---', error)
  55. )

then的链式调用和值穿透特性

promise的优势在于可以链式调用,比如我们在使用promise时,在then中返回了一个值,无论是什么值,我们都能在下一个then中获取到,这就是then的链式调用。而且当我们不在then中放入参数,promise.then().then(),那么其后面的then依旧可以得到之前then的返回值,这就是值的穿透。
这是如何实现的呢?
其实就是在每次调用then的时候,我们都创建一个新的promise对象,将之前的结果传给这个promise.then,那么再结合 Promise/A+ 规范梳理一下思路

  • then 的参数 onFulfilledonRejected 可以缺省,如果 onFulfilled 或者 onRejected不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;「规范 Promise/A+ 2.2.1、2.2.1.1、2.2.1.2」
  • promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise”;「规范 Promise/A+ 2.2.7」
  • 如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;
  • 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.2.7.2」
  • 如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」
  • 如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」
  • 如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;「规范 Promise/A+ 2.3.3.3.3」
  1. const PENDING = 'PENDING'
  2. const FULFILLED = 'FULFILLED'
  3. const REJECTED = 'REJECTED'
  4. /*
  5. * @description: 调用then时的主要逻辑
  6. * @param {*} promise2 调用then时返回的新promise
  7. * @param {*} x 传入then中的回调执行结果
  8. * @param {*} resolve Promise类中resolve函数,触发onFilledCallback
  9. * @param {*} reject Promise类中reject函数,触发onRejectedCallback
  10. * @return {*}
  11. */
  12. const resolvePromise = (promise2, x, resolve, reject) => {
  13. // 在promise2中返回了promise2,是个类型错误,需要结束掉 Promise/A+ 2.3.1
  14. // 例如 const temp = new Promise((resolve, reject) => {resolve()}).then(() => temp) 返回自己没有意义
  15. if (promise2 === x) return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  16. let lock = false // 只能将状态从pending到fulfilled或者rejected,所以resolve和reject只能调用一个,不能既调resolve又调reject
  17. if ((typeof x === 'object' && x !== null) || typeof then === 'function') {
  18. // then中返回的是对象或者是个function
  19. try {
  20. let then = x.then
  21. if (typeof then === 'function') {
  22. // 设置x这个对象/方法的then的执行环境
  23. then.call(x, res => {
  24. if (lock) return
  25. // res 本身可能仍然是个方法或对象,递归调用
  26. resolvePromise(promise2, res, resolve, reject)
  27. }, err => {
  28. if (lock) return
  29. lock = true
  30. reject(err)
  31. })
  32. } else {
  33. // 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
  34. resolve(x)
  35. }
  36. } catch (err) {
  37. reject(err)
  38. }
  39. } else {
  40. resolve(x)
  41. }
  42. }
  43. class Promise {
  44. constructor(executor) {
  45. this.status = PENDING;
  46. this.value = undefined;
  47. this.reason = undefined;
  48. this.onResolvedCallbacks = [];
  49. this.onRejectedCallbacks= [];
  50. let resolve = (value) => {
  51. if(this.status === PENDING) {
  52. this.status = FULFILLED;
  53. this.value = value;
  54. this.onResolvedCallbacks.forEach(fn=>fn());
  55. }
  56. }
  57. let reject = (reason) => {
  58. if(this.status === PENDING) {
  59. this.status = REJECTED;
  60. this.reason = reason;
  61. this.onRejectedCallbacks.forEach(fn=>fn());
  62. }
  63. }
  64. try {
  65. executor(resolve,reject) // 业务逻辑
  66. } catch (error) {
  67. reject(error)
  68. }
  69. }
  70. then(onFulfilled, onRejected) {
  71. //解决 onFufilled,onRejected 没有传值的问题
  72. //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
  73. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
  74. //因为错误的值要让后面访问到,所以这里也要抛出个错误,不然会在之后 then 的 resolve 中捕获(执行onRejected然后出错,被try-catch然后reject掉)
  75. onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
  76. // 每次调用 then 都返回一个新的 promise Promise/A+ 2.2.7
  77. let promise2 = new Promise((resolve, reject) => {
  78. if (this.status === FULFILLED) {
  79. //Promise/A+ 2.2.2
  80. //Promise/A+ 2.2.4 --- setTimeout
  81. setTimeout(() => {
  82. try {
  83. //Promise/A+ 2.2.7.1
  84. let x = onFulfilled(this.value);
  85. // x可能是一个proimise
  86. resolvePromise(promise2, x, resolve, reject); // 交给resolvePromise去resolve/reject
  87. } catch (e) {
  88. //Promise/A+ 2.2.7.2
  89. reject(e)
  90. }
  91. }, 0);
  92. }
  93. if (this.status === REJECTED) {
  94. //Promise/A+ 2.2.3
  95. setTimeout(() => {
  96. try {
  97. let x = onRejected(this.reason);
  98. resolvePromise(promise2, x, resolve, reject);
  99. } catch (e) {
  100. reject(e)
  101. }
  102. }, 0);
  103. }
  104. if (this.status === PENDING) {
  105. this.onResolvedCallbacks.push(() => {
  106. setTimeout(() => {
  107. try {
  108. let x = onFulfilled(this.value);
  109. resolvePromise(promise2, x, resolve, reject);
  110. } catch (e) {
  111. reject(e)
  112. }
  113. }, 0);
  114. });
  115. this.onRejectedCallbacks.push(()=> {
  116. setTimeout(() => {
  117. try {
  118. let x = onRejected(this.reason);
  119. resolvePromise(promise2, x, resolve, reject)
  120. } catch (e) {
  121. reject(e)
  122. }
  123. }, 0);
  124. });
  125. }
  126. });
  127. return promise2;
  128. }
  129. }
  130. const promise = new Promise((resolve, reject) => {
  131. reject('失败');
  132. }).then().then().then(data=>{
  133. console.log(data);
  134. },err=>{
  135. console.log('err',err);
  136. })

总结,链式操作,就是每次then的时候都返回了一个新的promise。
值穿透就是,每次then的时候,返回了新的promise,而这个promise引用了前一个Promise实例化对象中的属性state,这个state是由then之前的resolve或者reject来修改的,并相应记录了对应的value或者reson。在then的逻辑中,判断onFulfilled(this.value),onRejected(this.reason)的执行结果,如果是promise类,则递归调用,否则调用resolve/reject (这里的resolve/reject是then中new Promise中executor的参数,设置的是新Promise实例的state),而resolve/reject的调用,会将新Promise实例的state设置为fulfilled/reject,并设置对应value、reason,被下一个then调用的时候,重复这个过程。即
then引用着前一个promise实例的state、value、reason等属性,并设置新实例的state以备下一个then调用

API 实现

Promise.resolve

  1. // 默认产生一个成功的 promise
  2. static resolve(data){
  3. return new Promise((resolve,reject)=>{
  4. resolve(data);
  5. })
  6. }
  7. // promise.resolve 是具备等待功能的。如果参数是 promise 会等待这个 promise 解析完毕,在向下执行,所以这里需要在 resolve 方法中做一个小小的处理
  8. let resolve = (value) => {
  9. // ======新增逻辑======
  10. // 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析
  11. if(value instanceof Promise){
  12. // 递归解析
  13. return value.then(resolve,reject)
  14. }
  15. // ===================
  16. if(this.status === PENDING) {
  17. this.status = FULFILLED;
  18. this.value = value;
  19. this.onResolvedCallbacks.forEach(fn=>fn());
  20. }
  21. }

Promise.reject

  1. // 默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果。
  2. static reject(reason){
  3. return new Promise((resolve,reject)=>{
  4. reject(reason);
  5. })
  6. }

Promise.prototype.catch

  1. // Promise.prototype.catch 用来捕获 promise 的异常,就相当于一个没有成功的 then。
  2. Promise.prototype.catch = function(errCallback){
  3. return this.then(null,errCallback)
  4. }

Promise.prototype.finally

  1. // finally 表示不是最终的意思,而是无论如何都会执行的意思。
  2. // 如果返回一个 promise 会等待这个 promise 也执行完毕。如果返回的是成功的 promise,会采用上一次的结果;如果返回的是失败的 promise,会用这个失败的结果,传到 catch 中。
  3. Promise.prototype.finally = function(callback) {
  4. return this.then((value)=>{
  5. return Promise.resolve(callback()).then(()=>value)
  6. },(reason)=>{
  7. return Promise.resolve(callback()).then(()=>{throw reason})
  8. })
  9. }

Promise.all

promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)

  1. // then里记录value,在达到values数组长度时,返回所有的values
  2. Promise.all = function(values) {
  3. if (!Array.isArray(values)) {
  4. const type = typeof values;
  5. return new TypeError(`TypeError: ${type} ${values} is not iterable`)
  6. }
  7. return new Promise((resolve, reject) => {
  8. let resultArr = [];
  9. let orderIndex = 0;
  10. const processResultByKey = (value, index) => {
  11. resultArr[index] = value;
  12. if (++orderIndex === values.length) { // 够数了
  13. resolve(resultArr)
  14. }
  15. }
  16. for (let i = 0; i < values.length; i++) {
  17. let value = values[i];
  18. if (value && typeof value.then === 'function') {
  19. value.then((value) => {
  20. processResultByKey(value, i); // 存起来
  21. }, reject);
  22. } else {
  23. processResultByKey(value, i);
  24. }
  25. }
  26. });
  27. }

Promise.race

Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。

  1. Promise.race = function(promises) {
  2. return new Promise((resolve, reject) => {
  3. // 一起执行就是for循环
  4. for (let i = 0; i < promises.length; i++) {
  5. let val = promises[i];
  6. if (val && typeof val.then === 'function') {
  7. val.then(resolve, reject);
  8. } else { // 普通值
  9. resolve(val)
  10. }
  11. }
  12. });
  13. }

缺陷

因为Promise 是没有中断方法的,xhr.abort()、ajax 有自己的中断方法,axios 是基于 ajax 实现的;fetch 基于 promise,所以他的请求是无法中断的。
这也是 promise 存在的缺陷,我们可以使用 race 来自己封装中断方法:

  1. function wrap(promise) {
  2. // 在这里包装一个 promise,可以控制原来的promise是成功还是失败
  3. let abort;
  4. let newPromise = new Promise((resolve, reject) => { // defer 方法
  5. abort = reject;
  6. });
  7. let p = Promise.race([promise, newPromise]); // 任何一个先成功或者失败 就可以获取到结果
  8. p.abort = abort;
  9. return p;
  10. }
  11. const promise = new Promise((resolve, reject) => {
  12. setTimeout(() => { // 模拟的接口调用 ajax 肯定有超时设置
  13. resolve('成功');
  14. }, 1000);
  15. });
  16. let newPromise = wrap(promise);
  17. setTimeout(() => {
  18. // 超过3秒 就算超时 应该让 proimise 走到失败态
  19. newPromise.abort('超时了');
  20. }, 3000);
  21. newPromise.then((data => {
  22. console.log('成功的结果' + data)
  23. })).catch(e => {
  24. console.log('失败的结果' + e)
  25. })

promisify

promisify 是把一个 node 中的 api 转换成 promise 的写法。 在 node 版本 12.18 以上,已经支持了原生的 promisify 方法:const fs = require('fs').promises

  1. const promisify = (fn) => { // 典型的高阶函数 参数是函数 返回值是函数
  2. return (...args)=>{
  3. return new Promise((resolve,reject)=>{
  4. fn(...args,function (err,data) { // node中的回调函数的参数 第一个永远是error
  5. if(err) return reject(err);
  6. resolve(data);
  7. })
  8. });
  9. }
  10. }
  11. // node 中所有的 api 都转换成 promise 的写法
  12. const promisifyAll = (target) =>{
  13. Reflect.ownKeys(target).forEach(key=>{
  14. if(typeof target[key] === 'function'){
  15. // 默认会将原有的方法 全部增加一个 Async 后缀 变成 promise 写法
  16. target[key+'Async'] = promisify(target[key]);
  17. }
  18. });
  19. return target;
  20. }

拓展阅读: js中的异步错误捕获
从前的记录:promise.protype.then