实现一个Promise,了解Promise原理

一、前言

  • 其实 Promise 有多种规范,除了前面的 Promise A、promise A+ 还有 Promise/B,Promise/D。
  • 目前我们使用的 Promise 是基于 Promise A+ 规范实现的,感兴趣的移步 Promise A+规范了解一下,这里不赘述。

1. Promise基本使用

const promise = new Promise((resolve, reject) => {
   // 其他代码...
   if (/* 异步操作成功 */){
       resolve(value);
   } else {
       reject(error);
   }
})

promise.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

// 输出 resolve success

2. 思路

Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行

Promise 会有三种状态

  • Pending 等待
  • Fulfilled 完成
  • Rejected 失败

状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;

Promise 中使用 resolve 和 reject 两个函数来更改状态;

then 方法内部做但事情就是状态判断

  • 如果状态是成功,调用成功回调函数
  • 如果状态是失败,调用失败回调函数

二、实现同步版本

1. 传入执行器

  • executor 是实例化时传入的一个执行器,进入会立即执行。

  • 在执行器中传入resolvereject方法,可以在外部进行调用。

class MyPromise {
  constructor(executor){
     // 调用此方法就是成功
  	let resolve = () => {}
  	// 调用此方法就是失败
  	let reject = () => {}
    try {
       // executor 执行器,传入resolve和reject方法
       executor(resolve, reject) 
    }catch {
       reject(error)
    }
  }
}

状态与结果的管理

// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    // 初始化状态,为pending
    this.status = PENDING;

    // 结果/原因
    this.value = undefined;
    this.reason = undefined;

    // 修改状态,并执行成功回调
    let resolve = (value) => {
      // 只有状态是等待,才执行状态修改
      if (this.status === PENDING) {
        // 状态修改为成功,并保存结果
        this.status = FULFILLED;
        this.value = value;
      }
    }

    // 修改状态,并执行失败回调
    let reject = (reason) => {
      // 只有状态是等待,才执行状态修改
      if (this.status === PENDING) {
        // 状态修改为失败,保存失败原因
        this.status = REJECTED;
        this.reason = reason;
      }
    }

    // 立即执行,将 resolve 和 reject 函数传给使用者  
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)
    }
  }
}

2. then 的简单实现

class MyPromise {
  // 省略...
  // then方法实现
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    }
  }
}
module.exports = MyPromise

到这里就可以处理简单的同步问题了。

测试

const MyPromise = require('./my-promise.js')

const promise = new MyPromise((resolve, reject) => {
  resolve('success')
  reject('err')
})

promise.then(value => {
  console.log('resolve', value)
}, reason => {
  console.log('reject', reason)
})

执行node test,查看结果

三、加入异步逻辑

1. 缓存成功与失败回调

  • 执行到then方法时,耗时操作还没有结果,这个时候先将回调保存起来。
  • 这里使用数组保存,因为then方法可以调用多次。
class MyPromise {
  constructor(executor) {
    // 用于存储回调函数
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

  }
  // 执行回调
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      // 调用成功回调,并且把值返回
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // 调用失败回调,并且把原因返回
      onRejected(this.reason);
    } else if (this.status === PENDING) {
      // 存储回调函数
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason);
      })
    }
  }
}
  • 状态变更的时候触发
// 修改状态,并执行成功回调
let resolve = (value) => {
  // 只有状态是等待,才执行状态修改
  if (this.status === PENDING) {
    // 状态修改为成功,并保存值
    this.status = FULFILLED;
    this.value = value;
    // 执行回调
    this.onResolvedCallbacks.forEach(fn => fn());
  }
}

// 修改状态,并执行失败回调
let reject = (reason) => {
  // 只有状态是等待,才执行状态修改
  if (this.status === PENDING) {
    // 状态修改为失败,并保存原因
    this.status = REJECTED;
    this.reason = reason;
    // 执行回调
    this.onRejectedCallbacks.forEach(fn => fn());
  }
}
  • 到这里,就可以处理异步问题了。

测试

  • 使用定时器模拟耗时操作。
const MyPromise = require('./my-promise.js')

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 2000);
})

promise.then(value => {
  console.log(1)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(2)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(3)
  console.log('resolve', value)
})

结果

1
resolve success
2
resolve success
3
resolve success

这里实现了一个发布订阅模式,收集依赖 => 触发通知 => 取出依赖执行

2. then 的链式调用&值穿透特性

  1. then 方法要链式调用那么就需要返回一个 Promise 对象。
  2. then 方法里面 return 一个返回值作为下一个 then 方法的参数,这就是所谓的值的穿透
  3. then方法中抛出异常,需要把异常作为参数传递给下一个 then 的失败的回调中。
then(onFulfilled, onRejected) {
  //解决 onFufilled,onRejected 没有传值的问题
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
  //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
  onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

  // 每次调用 then 都返回一个新的 promise
  let promise2 = new Promise((resolve, reject) => {
    if (this.status === FULFILLED) {
      process.nextTick(() => { // 状态变化,添加微任务
        try {
          // 得到then方法的返回值
          let x = onFulfilled(this.value);
          resolve(x) // 先不考虑x为promise的情况
        } catch (e) {
          reject(e)
        }
      });
    }
    if (this.status === REJECTED) {
      process.nextTick(() => {
        try {
          let x = onRejected(this.reason);
          resolve(x) // 先不考虑x为promise的情况
        } catch (e) {
          reject(e)
        }
      });
    }
    if (this.status === PENDING) {
      this.onResolvedCallbacks.push(() => {
        process.nextTick(() => {
          try {
            let x = onFulfilled(this.value);
            resolve(x) // 先不考虑x为promise的情况
          } catch (e) {
            reject(e)
          }
        });
      });
      this.onRejectedCallbacks.push(() => {
        process.nextTick(() => {
          try {
            let x = onRejected(this.reason);
            resolve(x) // 先不考虑x为promise的情况
          } catch (e) {
            reject(e)
          }
        });
      });
    }
  })
  return promise2
}

这里使用process.nextTick()模拟异步

为什么 PromiseonFulfilledonRejected 必须是异步的?

我们知道 Promise handler 执行时机其实很容易定义

  1. 如果父亲 Promise 的状态不是 pending, 就立马执行
  2. 否则, 等到父亲 Promise 状态变化时才执行

因此, 理论上 Promise handler 执行不管是异步还是同步都可以完成这个逻辑。

我们把强制异步的标准派当做正方, 把认为同步异步都可以的当做反方。

反方认为: 如果本来是同步的逻辑被强制异步执行, 肯定是会有性能损失, 标准不应该强制异步。

正方则认为: 流程可预测(predictably)是最为重要的, 标准需要强制异步。

最终, 正方胜利, 标准制定者认为 Promise 主要解决的问题是 流程抽象规范一致性, 性能并不是首要考虑的。

我们希望代码执行顺序是完全可以预测的, 流程控制最首要的就是可控!

  • 而回调(then方法中的)如果可能同步、也可能异步,执行顺序就变成了不确定的。

例子1

var foo
promise.then(() => {
  foo = {}
})
console.log(foo.bar) // always crash
// 任何一个 Promise 使用者都应该知道这段代码是会报错的

例子2

console.log(1)
var promise = new Promise(resolve => {
  console.log(2)
  resolve()
})
promise.then(() => {
  console.log(3)
})
console.log(4)
// 我们确保执行输出顺序永远是 `1, 2, 4, 3`

3. then返回值是一个Promise

我们继续完成Promise,再结合 Promise/A+ 规范梳理一下思路:

  1. 如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」
  2. 如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」
  3. 如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;「规范 Promise/A+ 2.3.3.3.3」
const resolvePromise = (promise2, x, resolve, reject) => {
  // 不允许自己引用自己,这样会陷入死循环
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // 一个then方法防止调用多个操作
  let called;

  // x是一个对象或者函数,且不是null
  if ((typeof x === 'object' && x != null) || typeof x === 'function') {
    try {
      // 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)
      let then = x.then;
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') {
        // 就让then执行 第一个参数是this指向   后面是成功的回调 和 失败的回调
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, r => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        // 如果 x.then 是个普通值就直接返回 resolve 作为结果(能调用.then,但不是函数,说明不是promise,只是和promise类似)
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 如果 x 是个普通值就直接返回 resolve 作为结果
    resolve(x)
  }
}
  • then方法中引入该函数
x.then(resolve, reject) // 先不考虑x为promise的情况
// 修改为
resolvePromise(promise2, x, resolve, reject);

测试

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject('失败');
  }, 1000);
})

promise.then().then().then(data => {
  console.log(data);
}, err => {
  console.log('err', err);
})

打印结果

"失败 err"
  • 至此,我们已经完成了 promise 最关键的部分:then 的链式调用和值的穿透。
  • 搞清楚了 then 的链式调用和值的穿透,你也就搞清楚了 Promise。

四、测试 Promise 是否符合规范

  • Promise/A+规范提供了一个专门的测试脚本,可以测试所编写的代码是否符合Promise/A+的规范。
  • 首先,在 promise 实现的代码中,增加以下代码:
MyPromise.defer = MyPromise.deferred = function() {
  let dfd = {}
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}

下载依赖

yarn add promises-aplus-tests

添加命令

// package.json
"scripts": {
	"test": "promises-aplus-tests my-promise.js"
}

执行yarn test,查看结果

在这里插入图片描述

通过全部872 条测试用例。

五、Promise 的 API

  • Promise.resolve()
  • Promise.reject()
  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.all()
  • Promise.race()

1. Promise.resolve

  • 默认产生一个成功的 promise。
static resolve(data){
  return new Promise((resolve,reject)=>{
    resolve(data);
  })
}
  • 这里需要注意的是,promise.resolve 是具备等待功能的。(异步)
  • 如果参数是 promise 会等待这个 promise 解析完毕,在向下执行。
let resolve = (value) => {
  // ======新增逻辑======
  // 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析
  if(value instanceof Promise){
      // 递归解析 
      return value.then(resolve,reject)
  }
  // ===================
  if(this.status ===  PENDING) {
    this.status = FULFILLED;
    this.value = value;
    this.onResolvedCallbacks.forEach(fn=>fn());
  }
}

2. Promise.reject

默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果。

static reject(reason){
  return new Promise((resolve,reject)=>{
    reject(reason);
  })
}

3. Promise.prototype.catch

用来捕获 promise 的异常,就相当于一个没有成功的 then

Promise.prototype.catch = function(errCallback){
  return this.then(null,errCallback)
}

4. Promise.prototype.finally

不管成功还是失败都会执行。

Promise.prototype.finally = function(callback) {
  return this.then((value)=>{
    return Promise.resolve(callback()).then(()=>value)
  },(reason)=>{
    return Promise.resolve(callback()).then(()=>{throw reason})
  })  
}

5. Promise.all

Promise.all = function(values) {
  if (!Array.isArray(values)) {
    const type = typeof values;
    return new TypeError(`TypeError: ${type} ${values} is not iterable`)
  }
  
  return new Promise((resolve, reject) => {
    let resultArr = [];
    let orderIndex = 0;
    const processResultByKey = (value, index) => {
      resultArr[index] = value;
      if (++orderIndex === values.length) {
          resolve(resultArr)
      }
    }
    for (let i = 0; i < values.length; i++) {
      let value = values[i];
      if (value && typeof value.then === 'function') {
        value.then((value) => {
          processResultByKey(value, i);
        }, reject);
      } else {
        processResultByKey(value, i);
      }
    }
  });
}

6. Promise.race

用来处理多个请求,克隆最快的那一个,但是不会取消其他的promise。

Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    // 一起执行就是for循环
    for (let i = 0; i < promises.length; i++) {
      let val = promises[i];
      if (val && typeof val.then === 'function') {
        val.then(resolve, reject);
      } else { // 普通值
        resolve(val)
      }
    }
  });
}

参考资料

手写Promise

https://github.com/qiruohan/article/blob/master/promise/promise.js

promise/core.js at master · then/promise · GitHub

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值