Promise从基本语法到源码实现

promise 语法

只有promise对象后面可以接.then,如下所示,当执行A函数时,会立即返回一个 promise 对象,只不过此时 promise 的状态为 pending,此时then不会执行,一秒后promise状态变为reslove后,then里面的回调才会执行。

js 复制代码
let count = 1

function A() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      count = 100
      resolve('ok')    // 将 promise 的状态改为 resolve
    }, 1000)
  })
}

function B() {
  console.log(count)
}

// 0ms后 A 就返回了一个 promise 对象,但是这个promise对象的状态为 pending
// 1000ms后 A 的 promise 对象状态变为 resolved
// then 中的回调函数 B 才会执行  
A().then((res) => {
  B()
  console.log(res);   // 输出 ok
})

由此可得,return一个promise对象是立即执行的,then里面的回调只有在该promise状态变更才会执行,知道了以上原理,再比较一下下面三种写法:

js 复制代码
let count = 1

function A() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      count = 100
      resolve()    
    }, 1000)
  })
}

function B() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      count = 200
      resolve()
    }, 500)
  })
}

function C() {
  console.log(count);
}
js 复制代码
// 第一种:
A().then(() => {
  B().then(() => {
    C()
  })
})

每个.then()回调函数的执行是基于前一个Promise的状态,第二个then只有当执行完B函数返回reslove才会改变promise的状态变为reslove,然后才执行 .then 里面的回调。在这里,C()函数的执行依赖于B()函数返回的Promise被解决。所以输出 200.

js 复制代码
// 第二种
A()
  .then(() => {
    B()
  })
  .then(() => {
    C()
  })

第二个 .then 是接在第一个.then后面的,而不是B函数后面,每个then的返回值是一个新的promise对象,第二个then接收的是第一个then的返回值,当1s后执行完A函数时,第一个then状态变为reslove,而此时第二个then接收第一个then返回的的promise状态reslove,所以此时B跟C函数一起执行,C函数不耗时,所以直接打印100,0.5s后B执行将count改为200,但是此时已经打印完了,所以最终结果 100.

js 复制代码
// 第三种
A()
  .then(() => {
    return B()
  })
  .then(() => {
    C()
  })

这时候第一个 .then 回调里面有 return,这时候第二个then就会以第一个then回调里面return出的为上一个的promise状态,所以输出 200.

总的来说:then 方法的返回值是一个新的 promise,状态为 pending,then 的 pending 状态会根据上一个 promise 状态的修改而修改。

promise 源代码

要想打造一个promise就需要在MyPromise类里面添加以一个constructor构造器,参数为传进来的回调函数,因为Promise是return new Promise((resolve, reject) => {}这样使用的。所以需要传入两个函数体作为executor的参数,且里面是可以resolve出来值的,reject出来的值给catch回调函数调用,所以再给两个函数补个形参,如下;

js 复制代码
class MyPromise {
  constructor(executor) {  // 传进来的函数体
    this.value = null     // 成功的结果
    this.reject = null    // 失败的原因
    const reslove = (value) => {
      this.value = value
    }
    const reject = () => {
      this.reject = reject
    }
    executor(reslove, reslove)      //  参数为两个函数体 reslove reject
  }
}

而且promise状态一经变更就不会再变回去了,比如先reslove再reject,就只会变更为reslove状态,而不会再变更为reject状态,但是会触发reject而不会变更状态。这个实现就是添加一个初始状态this.status === pending,当reslove跟reject函数里面更改状态时,先判断当前状态是否为pending,是这个状态则表示状态还未确定,然后改变状态,这样就可以得到会执行但是不会变更状态的效果了。

js 复制代码
class MyPromise {
  constructor(executor) {  // 传进来的函数体
    this.status = 'pending'  // 初始状态为 pending,表示 promise 对象的状态还未被确定
    this.value = null     // 成功的结果
    this.reject = null    // 失败的原因
    const reslove = (value) => {
      this.value = value
      if (this.status === 'pending') {
        this.status = 'fulfilled'   // 状态变为 fulfilled
      }
    }
    const reject = () => {
      this.reject = reject
      if (this.status === 'pending') {
        this.status = 'rejected'   // 状态变为 rejected
      }
    }
    executor(reslove, reslove)      //  参数为两个函数体 reslove reject
  }
}

再注意一个细节,then后面的函数是根据promise状态来触发的,当调用reslove或reject更改状态时才触发,于是直接将then后面的回调函数放到这reslove或reject函数体里面触发即可,同时传入的参数刚好是前面reslove或reject的值。

需要用一个数组来存放回调函数,因为如果是then接在then后面,且前面的then里面没有return,如下;

js 复制代码
A()
  .then(() => {
    B()
  })
  .then(() => {
    C()
  })

则A函数返回的promise状态的改变会同时触发函数B和C。

所以最后的promise源码:

js 复制代码
class MyPromise {
  constructor(executor) {  // 传进来的函数体
    this.status = 'pending'  // 初始状态为 pending,表示 promise 对象的状态还未被确定
    this.value = null     // 成功的结果
    this.reason = null    // 失败的原因
    this.onFulfilledCallbacks = []  // 存放成功的回调函数
    this.onRejectedCallbacks = []  // 存放失败的回调函数
    const reslove = (value) => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'   // 状态变为 fulfilled
        this.onFulfilledCallbacks.forEach(fn => fn(value))
      }
    }
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'   // 状态变为 rejected
        this.onRejectedCallbacks.forEach(fn => fn(reason))
      }
    }
    executor(reslove, reslove)      //  参数为两个函数体 reslove reject
  }
}

总的来说,就是定义两个函数(函数体里面存形参)作为实参传给executor,然后记住状态改变,以及触发函数。

then 源代码

至于then里面回调函数是如何放入这个数组中的是then函数干的事了。then函数是挂在promise对象的原型上。所以直接在MyPromise类中定义一个then函数即可。这个函数就挂在原型上。

首先then后面是可以接收两个回调的。第二个回调函数完全等同于.catch的功能。

js 复制代码
A()
  .then(
    (res) => {
      B()
    },
    (err) => {
      console.log(err);   // reject出来的值
    }
  )

then 原理

  1. 接收两个参数,第一个是成功的回调,第二个是失败的回调
  2. 返回一个新的promise
  3. 当then执行到的时刻,then前面的promise状态已经变更为fulfilled或rejected,then中的回调函数由then自己执行
  4. 当then执行到的时刻,then前面的状态变为pending,then中的回调函数会先被存放,等待promise状态变更为fulfilled或rejected时再由resolve或reject函数执行

第三点就发生在当前面的promise对象不耗时的时候,promise里面放同步代码的时候,里面的回调就有then函数自己触发,就不需要由promise对象里面的resolve函数触发,如下;

js 复制代码
function A() {
  return new MyPromise((resolve, reject) => {
    resolve('ok')
  })
}

function B() {
  console.log('b');
}

A.then(
  B()
)

然后then的完整源代码如下:

js 复制代码
class MyPromise {
  // constructor(executor) { ... }
  then(onFulfilled, onRejected) {
    // 判断传入的两个参数是否为 函数体
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value  // value => value 是一个函数体
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    // 返回一个新的promise对象
    let newPromise = new MyPromise((resolve, reject) => {
      if (this.status === 'fulfilled') {   // this 是 then 的
        setTimeout(() => {
          try {
            // .then是异步微任务,由于很难打造成微任务,于是可以用定时器打造成异步任务
            const result = onFulfilled(this.value)   
            // 也就是当回调里面有return 出一个对象是promise对象时返回这个对象
            if (result instanceof Promise) {
              newPromise = result
            }
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason)
            if (result instanceof Promise) {
              newPromise = result
            }
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === 'pending') {
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => {
            try {
              const result = onFulfilled(value)
              if (result instanceof Promise) {
                newPromise = result
              }
              resolve(result)
            } catch (error) {
              reject(error)
            }
          })
        })
        // 将函数push进成功的成功的回调函数数组,让Promise里面的reslove调用,
        // 也就是当前面的promise返回状态 fulfilled 时调用
        this.onRejectedCallbacks.push((reason) => {
          setTimeout(() => {
            try {
              const result = onRejected(reason)
              if (result instanceof Promise) {
                newPromise = result
              }
              resolve(result)
            } catch (error) {
              reject(error)
            }
          })
        })
      }
    })
    return newPromise
  }
  
  catch(onRejected) {
    return this.then(null, onRejected);
  }
}
相关推荐
264玫瑰资源库5 分钟前
网狐飞云娱乐三端源码深度实测:组件结构拆解与部署Bug复盘指南(附代码分析)
java·开发语言·前端·bug·娱乐
济南壹软网络科技-专注源码开发数十年!21 分钟前
盲盒源码_盲盒系统_盲盒定制开发 盲盒搭建前端教程
开发语言·前端·uni-app·php
大学生小郑1 小时前
Go语言八股文之Map详解
面试·golang
天天进步20151 小时前
React Hooks 深入浅出
javascript·react.js·ecmascript
哟哟耶耶1 小时前
react-13react中外部css引入以及style内联样式(动态className与动态style)
前端·css·react.js
A_aspectJ1 小时前
【Bootstrap V4系列】学习入门教程之 组件-卡片(Card)高级用法
前端·学习·bootstrap
前端 贾公子2 小时前
手写 Vue 源码 === Effect 机制解析
javascript·vue.js·ecmascript
DT——2 小时前
ECMAScript 6(ES6):JavaScript 现代化的革命性升级
前端·javascript·ecmascript
搞不懂语言的程序员2 小时前
Redis面试 实战贴 后面持续更新链接
数据库·redis·面试
qq_400552002 小时前
【React Hooks原理 - useCallback、useMemo】
前端·react.js·前端框架