重温Promise特性,简单手写一个

一、什么是Promise?

Promise是js异步编程的解决方案。从语法上看,Promise是一个构造函数;从功能上看,promise对象用来封装一个异步操作并可以获取其结果;

每个Promise实例对象都有三个状态,分别为pending(初始,状态未改变)、fulfilled(成功)、rejected(失败);

promise对象的状态只能从pending到fulfilled或从pending到rejected变化,并且状态改变后不再改变;

二、为什么使用Promise?

Promise指定回调函数的方式或时机更灵活

在传统回调函数中,必须在异步任务启动前指定回调函数; 而在使用Promise时,我们通常先启动异步任务,然后返回一个promise对象,最后通过then/catch指定回调函数(甚至可以在异步任务结束后指定回调函数);

javascript 复制代码
/* 传统回调函数 */
  $.ajax({
    url: '/user/info',
    method: 'post',
    success: function() {
      // 请求成功回调,需要在在开启请求之前指定
    }
  })
  /* promise指定回调 */
  // 1. 开启异步任务,返回一个promise对象
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, 200);
  })
  // 2. 指定回调函数
  promise.then(
    value => {console.log('resolve', value)},
    reason => {console.log('reject', reason)}
  )
  // 3.异步任务结束后指定回调函数
  setTimeout(() => {
    promise.then(
      value => {console.log('resolve', value)},
      reason => {console.log('reject', reason)}
    )
  }, 2000)
Promise支持链式调用,可以解决回调地狱问题

什么是回调地狱? 回调函数嵌套调用,上一个回调函数返回的结果是嵌套回调函数执行的条件; 回调地狱的问题? 不便于开发者阅读,不便于异常排查; 解决方案?Promise链式调用; js异步任务的终极解决方案? async/await;

三、理解Promise的一些关键性问题

如何改变promies的状态?

上面已经提到了promise的状态有三种,pending、fulfilled、rejected; 一般说到改变状态我们都会想到resolve()和reject()这两个方法,其实还有一种改变状态的方式,这里总结一下:

  • 使用resolve() 状态由pending变为fulfilled;
  • 使用reject() 状态由pending变为rejected;
  • throw Error('error')/ throw 任意值 状态由pending;
一个promise实例指定多个成功/失败的回调,都会被调用吗?

当promise状态改变时,对应的回调函数都会被调用;

javascript 复制代码
const promise = new Promise((resolve, reject) => {
  resolve(1) // 触发所有成功状态的回调函数
})
// 指定第一个回调
promise.then(
  value => {console.log(value)}, // 输出1
  reason => {console.log(reason)}
)
// 指定第二个回调
promise.then(
  value => {console.log(value)}, // 输出1
  reason => { console.log(reason) }
)
改变promise状态和指定回调函数谁先谁后?

都有可能,正常情况下先指定回调函数再改变状态,但也可以先改变状态再指定回调函数; 如何先改变状态再指定回调函数?

  • 在执行器中同步调用resolve()/reject();
  • 延迟更长的时间调用then,例如使用定时器; 什么时候才能得到数据?当调用回调函数时,都可以在回调函数中得到数据;
promise.then()返回的新promise对象的状态由谁决定?

总结:由then指定的回调函数(成功/失败)的结果决定; 具体:

  • 如果抛出异常,新promise对象的状态变为rejected,结果是抛出的异常
  • 如果返回的是非promise的任意值,新promise对象的状态变为fulfilled,结果是当前任意值;
  • 如果返回另一个promise对象,此promise对象的状态及结果就会成为新promise的状态和结果;
javascript 复制代码
new Promise((resolve, reject) => {
  resolve(1) // 输出顺序:1 => 3 => 5
  // reject(1) // 输出顺序:2 => 4 => 5
}).then(
  value => {
    console.log('onResolve1', value) // 顺序1
    return Promise.resolve(value + 2) // 返回一个新的promise,且状态为fulfilled
  },
  reason => {
    console.log('onReject1', reason) // 顺序2
    throw 3 // 抛出异常
  }
).then(
  value => { console.log('onResolve2', value) // 顺序3 },
  reason => { console.log('onReject2', reason) // 顺序4 }
).then(
  value => { console.log('onResolve3', value) // 顺序5 },
  reason => { console.log('onReject3', reason) // 顺序6 }
)
Promise怎么串联多个操作任务?

promise.then()返回一个新的promise对象,通过then的链式调用,可以串联多个同步/异步任务; 其中串联异步任务需要new一个promise对象;

javascript 复制代码
new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('执行任务1(异步)')
    resolve(1)
  }, 1000);
}).then(value => {
  console.log('任务1的结果:', value)
  console.log('执行任务2(同步)')
  return 2
}).then(value => {
  console.log('任务2的结果: ', value)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('执行任务3(异步)')
      resolve(3)
    }, 2000);
  })
}).then(value => {
  console.log('任务3的结果', value)
})
Promise的异常传透怎么实现的?

当我们使用then的链式调用时,通常会在调用链的最后指定失败的回调catch; 当catch前面的then中有一环出现了异常或者状态变为rejected,都会通过then依次传递到catch中,注意并不是直接跳到catch; 因为我们统一用catch做异常处理了,所以一般我们不在then中写rejected回调,这时rejected的回调默认为 reason => { throw reason };

javascript 复制代码
new Promise((resolve, reject) => {
  // resolve(1)
  reject(1)
}).then(
  value => {
    console.log('onResolve1', value)
    return 2
  },
  reason => { throw reason } // 失败回调的默认值, 将异常传透给catch
).then(
  value => { console.log('onResolve2', value) }
).catch(
  reason => { console.log('onReject', reason) }
)
怎么中断promise链?

在回调函数中返回一个状态为pending的promise,可以中断promise链;

javascript 复制代码
new Promise((resolve, reject) => {
  // resolve(1)
  reject(1)
}).then(
  value => { console.log('onResolve1', value) }
).catch(reason => {
  console.log('onReject', reason)
  return new Promise(() => {}) // 返回一个pending状态的promise,中止promise链
}).then(
  value => { console.log('onResolve3', value) }
)

四、源码实现

javascript 复制代码
(function() {

  const PENDING = 'pending'
  const FULFILLED = 'fulfilled'
  const REJECTED = 'rejected'

  /* 
    Promise类
    excutor:执行器函数(同步)
  */
  class Promise {
    constructor(excutor) {
      const _this = this
      this.status = PENDING // promise实例对象的状态,初始值为pending
      this.data = undefined // 存储结果数据的属性
      this.callbacks = [] // 回调函数,每个元素的数据解构: { onResolved() {}, onRejected() {} }

      // 触发成功回调
      function resolve(value) {
        // 当前状态不是pending,结束
        if (_this.status !== PENDING) return false
        // 修改当前状态为fulfilled
        _this.status = FULFILLED
        // 保存数据结果
        _this.data = value
        // 如果由待执行的回调函数,立即异步执行回调函数的onResolved
        if (_this.callbacks.length) {
          setTimeout(() => {
            _this.callbacks.forEach(callbackObj => {
              callbackObj.onResolved(value)
            })
          })
        }
      }

      // 触发失败回调
      function reject(reason) {
        // 当前状态不是pending,结束
        if (_this.status !== PENDING) return false
        // 修改当前状态为rejected
        _this.status = REJECTED
        // 保存数据结果
        _this.data = reason
        // 如果有待执行的回调函数,立即异步执行回调函数的onRejected
        if (_this.callbacks.length) {
          setTimeout(() => {
            _this.callbacks.forEach(callbackObj => {
              callbackObj.onRejected(reason)
            })
          })
        }
      }

      // 处理执行器异常
      try {
        excutor(resolve, reject)
      } catch (error) {
        reject(error)
      }
    }
    /* 
      then方法
      指定成功和失败的回调
      返回一个promise
    */
    then(onResolved, onRejected) {
      const _this = this

      onResolved = typeof onResolved === 'function' ? onResolved : value => value // 向下传递value
      // 指定默认的失败回调onRejected(实现异常传透的关键)
      onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

      return new Promise((resolve, reject) => {
        // 调用指定回调函数处理,根据执行结果,改变return的promise的状态
        function handle(callback) {
          /* 
            1. 如果抛出异常,return的Promise的结果就是失败的,reason就是error
            2. 如果返回的结果不是promise类型,return的Promise的结果就是成功的,value就是该返回的结果
            3. 如果是promise,return的Promise的结果就是该promise的结果
          */
          try {
            const result = callback(_this.data)
            if (result instanceof Promise) {
              // 3. 返回的结果是promise,return的Promise的结果就是该promise的结果
              result.then(
                value => resolve(value),
                reason => reject(reason)
              )
              // result.then(resolve, reject)
            } else {
              // 2. 返回的结果不是promise类型,return的Promise的结果就是成功的,value就是该返回的结果
              resolve(result)
            }
          } catch (error) {
            // 1. 抛出异常,return的Promise的结果就是失败的,reason就是error
            reject(error)
          }
        }
        // 状态还没改变,先将回调函数存储起来,用于状态改变时调用
        if (this.status === PENDING) {
          this.callbacks.push({
            onResolved () {
              handle(onResolved)
            },
            onRejected () {
              handle(onRejected)
            }
          })
        } else if (this.status === FULFILLED) {
          // 状态是fulfilled,异步执行成功回调
          setTimeout(() => {
            handle(onResolved)
          })
        } else {
          // 状态是rejected,异步执行失败回调
          setTimeout(() => {
            handle(onRejected)
          })
        }
      })
    }

    /* 
      catch方法
      指定失败的回调
      返回一个promise
    */
    catch(onRejected) {
      return this.then(undefined, onRejected)
    }

    /* 
      Promise的静态方法resolve
      返回一个指定value的成功/失败的promise
    */
    static resolve = function(value) {
      return new Promise((resolve, reject) => {
        // value是一个promise
        if (value instanceof Promise) {
          value.then(resolve, reject)
        } else { // value不是一个promise
          resolve(value)
        }
      })
    }

    /* 
      Promise的静态方法reject
      返回一个指定reason的失败的promise
    */
    static reject = function(reason) {
      return new Promise((resolve, reject) => {
        reject(reason)
      })
    }

    /* 
      Promise的静态方法all
      返回一个promise,当多有promise都成功时才成功,有一个失败则失败
    */
    static all = function(promises) {
      // 定义一个指定长度的数组,用于存储resolve的值
      const values = new Array(promises.length)
      // 计数,用于记录成功的promise次数
      let resolveCount = 0
      return new Promise((resolve, reject) => {
        promises.forEach((p, index) => {
          Promise.resolve(p).then(
            value => {
              values[index] = value
              resolveCount ++
              // 如果全部都成功了,将返回的promise改为成功
              if (resolveCount === promises.length) {
                resolve(values)
              }
            },
            // 有一个promise失败,将返回的promise改为失败
            reason => {
              reject(reason)
            }
          )
        })
      })
    }

    /* 
      Promise的静态方法race
      返回一个promise,其结果由第一 个完成的promise决定
    */
    static race = function(promises) {
      return new Promise((resolve, reject) => {
        promises.forEach(p => {
          Promise.resolve(p).then(
            // 有一个promise成功,将返回的promise改为成功
            value => {
              resolve(value)
            },
            // 有一个promise失败,将返回的promise改为失败
            reason => {
              reject(reason)
            }
          )
        })
      })
    }
  }
  // 暴露Promise
  window.Promise = Promise
})()
相关推荐
光影少年7 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_8 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu108301891110 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾12 分钟前
前端基础-html-注册界面
前端·算法·html
Dragon Wu14 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym19 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫20 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫24 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat25 分钟前
前端性能优化2
前端
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js