手写实现Promise及其Api

本文已参与[新人创作礼]活动,一起开启掘金创作之路

手写实现Promise及其Api

在大厂前端面试中,我们经常会碰到这些问题:“你了解Promise的Api有哪些”、“Promise.all和Promise.race是什么”、“你能实现一个Promise.all(Promise.race)吗”等等

楼主在近期的暑期实习面试中也被询问到了手写Promise.all

因此,今天在此好好总结一下Promise~

Promise的前世今生

Promise出现的原因

首先,我们知道,在node里处理异步请求是通过回调函数的方式处理的,在Promise出现之前,当我们需要处里多个异步请求嵌套时,代码往往是这样的:

const fs = require('fs');
​
fs.readFile('./user.txt', 'utf8', function (err, data) {
    fs.readFile(data, 'utf8', function (err, data) {
        fs.readFile(data, 'utf8', function (err, data) {
            console.log(data);
        })
    })
})
复制代码

为了拿到回调的结果,我们必须一层一层的嵌套,可以说是相当恶心了。而且基本上我们还要对每次请求的结果进行一系列的处理,使得代码变的更加难以阅读和难以维护,这就是传说中臭名昭著的回调地狱

产生回调地狱的原因有两点:

1.嵌套调用,第一个函数的输出往往是第二个函数的输入;

2.处理多个异步请求并发,开发时往往需要同步请求最终的结果。

根据产生回调地狱的原因,问题的解决方向就很清晰了:

  1. 消除嵌套:=> Promise的链式调用
  2. 合并多个任务的请求结果:=> Promise.all

如果用Promise解决上面的问题会是这样的:

扫描二维码关注公众号,回复: 13791096 查看本文章
const fs = require('fs');
​
const read = (filename) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) reject(err);
            resolve(data);
    })
}
                       
read('./user.txt').then(data => {
     return read(data);
}).then(data => {
     return read(data); 
}).then(data => {
    console.log(data);        
}).catch(err => {
    console.error(err);      
})
复制代码

很明显,通过Promise链式调用的形势大大改善了代码的阅读性可维护性。也就是说,Promise 解决的是异步编码风格的问题。

什么是Promise?

上面介绍了Promise的由来是为了解决异步编码风格的问题,那么,什么是Promise,以及如何使用Promise?

描述

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled) : 意味着操作成功完成。
  • 已拒绝(rejected) : 意味着操作失败。

待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled) ,要么会通过一个原因(错误)被拒绝(rejected) 。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。

因为 Promise.prototype.thenPromise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。

img

更加详细了解Promise可参考MDN文档:Promise

若想了解 promises 的工作方式以及如何使用它们,建议参考使用 promises

Promise的API们

静态方法

  • Promise.all(iterable)

    这个方法返回一个新的promise对象,该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。(可以参考jQuery.when方法---译者注)

  • Promise.allSettled(iterable)

    等到所有promises都已敲定(settled)(每个promise都已兑现(fulfilled)或已拒绝(rejected))。 返回一个promise,该promise在所有promise完成后完成。并带有一个对象数组,每个对象对应每个promise的结果。

  • Promise.any(iterable)

    接收一个Promise对象的集合,当其中的一个 promise 成功,就返回那个成功的promise的值。

  • Promise.race(iterable)

    当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

  • Promise.reject(reason)

    返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法

  • Promise.resolve(value)

    返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。通常而言,如果您不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

    Promise的使用

首先,我们先回顾一下Promise的基础使用

const p1 = new Promise((resolve, reject) => {
  console.log('create a promise');
  resolve('success')
})
​
console.log('after create a promise');
​
const p2 = p1.then(res => {
  console.log(res);
  throw new Error('failed')
})
​
const p3 = p2.then(res => {
  console.log('success:', res);
}, err => {
  console.log('failed:', err);
})
复制代码

输出:

create a promise
after create a promise
success
failed: Error: failed
复制代码
  • 首先我们在调用 Promise 时,会返回一个 Promise 对象。
  • 构建 Promise 对象时,需要传入一个 executor 函数,Promise 的主要业务流程都在 executor 函数中执行。
  • 如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。
  • Promise 的状态不可逆,同时调用 resolve 函数和 reject 函数,默认会采取第一次调用的结果。

以上简单介绍了 Promise 的一些主要的使用方法,结合 Promise/A+ 规范,我们可以分析出 Promise 的基本特征:

  1. promise 有三个状态:pendingfulfilled,or rejected;「规范 Promise/A+ 2.1」
  2. new promise时, 需要传递一个executor()执行器,执行器立即执行;
  3. executor接受两个参数,分别是resolvereject
  4. promise 的默认状态是 pending
  5. promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」
  6. promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」
  7. promise 只能从pendingrejected, 或者从pendingfulfilled,状态一旦确认,就不会再改变;
  8. promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」
  9. 如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promisevalue
  10. 如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promisereason
  11. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected

实现一个Promise

实现一个符合Promise A+ 规范的Promise

PromiseA+规范

// 定义Promise的三个状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

const resolvePromise = (promise2, x, resolve, reject) => {
   // 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise  Promise/A+ 2.3.1
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // Promise/A+ 2.3.3.3.3 只能调用一次
  let called;
  // 后续的条件要严格判断 保证代码能和别的库一起使用
  if ((typeof x === 'object' && x != null) || typeof x === 'function') { 
    try {
      // 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)  Promise/A+ 2.3.3.1
      let then = x.then;
      if (typeof then === 'function') { 
        // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
        then.call(x, y => { 
          if (called) return;
          called = true;
          // 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
          resolvePromise(promise2, y, resolve, reject); 
        }, err => {
          // 只要失败就失败 Promise/A+ 2.3.3.3.2
          if (called) return;
          called = true;
          reject(err);
        });
      } else {
         // 如果 x.then 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      // Promise/A+ 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 如果 x 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.4 
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    // Promise的状态
    this.status = PENDING;
    // 存放成功状态的值,默认为 undefined
    this.value = undefined;
    // 存放失败状态的值,默认为 undefined
    this.reason = undefined;
    // 存放成功的回调函数
    this.onResolvedCallbacks = [];
    // 存放失败的回调函数
    this.onRejectedCallbacks = [];

    // 成功时调用该方法
    const resolve = value => {
      // 如果参数是promise 等待这个promise解析完毕 再往后执行
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }

      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次执行回调函数数组中的函数
        this.onResolvedCallbacks.forEach(fn => fn()); 
      }
    }

    // 失败时调用该方法
    const reject = reason => {
      // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 依次执行回调函数数组中的函数
        this.onRejectedCallbacks.forEach(fn => fn()); 
      }
    }

    try {
      // 传入resolve和reject
      executor(resolve, reject);
    } catch (error) {
      // 出现异常直接reject
      reject(error);
    }
  }

  then(onFulfilled, onRejected) {
    //解决 onFufilled,onRejected 没有传值的问题
    //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
     // 每次调用 then 都返回一个新的 promise  Promise/A+ 2.2.7 -- 链式调用
    const promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        //Promise/A+ 2.2.2
        //Promise/A+ 2.2.4 --- setTimeout
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            // x可能是一个proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            // x可能是一个proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.status === PENDING) {
        // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        
        // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        })
      }
      
    })
    return promise2;
  }

  catch(errorCallback) {
    return this.then(null, errorCallback);
  }

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

  // 默认产生一个成功的 promise。
  static resolve(data) {
    return new Promise((resolve, reject) => {
      resolve(data);
    })
  }

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

  static all(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) => {
      const resArr = [];
      let resolvedCount = 0;
      const processResultByKey = (value, index) => {
        resArr[index] = value;
        if (++resolvedCount === promises.length) resolve(resArr);
      }

      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(res => {
            processResultByKey(res, i);
          }, reject);
        } else {
          processResultByKey(promise, i);
        }
      }
    })
  }

  static race(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(resolve, reject);
        } else {
          resolve(promise);
        }
      }
    })
  }

  static allSettled(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) => {
      const result = new Array(promises.length);
      let count = 0;
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(res => {
            result[i] = { status: 'fulfilled', value: res };
            count++;
            if (count === promises.length) resolve(result);
          }).catch(err => {
            result[i] = { status: 'rejected', reason: err };
            count++;
            if (count === promises.length) resolve(result);
          })
        } else {
          result[i] = { status: 'fulfilled', value: promise };
          count++;
          if (count === promises.length) resolve(result);
        }
      }
    })
  }

  // 返回第一个成功结果,全部失败才返回失败;
  static any(promises) {
    if (!Array.isArray(promises)) {
      const type = typeof promises;
      return new TypeError(`TypeError: ${type} ${promises} is not iterable`);
    }

    return new Promise((resolve, reject) => {
      const rejectedArr = [];
      let count = 0;
      if (promises === null || promises.length === 0) {
        reject('无效的any');
      }
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise && typeof promise.then === 'function') {
          promise.then(res => {
            resolve(res);
          }, err => {
            rejectedArr[i] = err;
            count++;
            if (count === promises.length) {
              reject(rejectedArr);
            }
          })
        } else {
          // 处理普通值 直接成功
          resolve(promise);
        }
      }
    })
  }

}

// 测试 Promise 是否符合规范
// npm install -g promises-aplus-tests
// promises-aplus-tests promise.js
Promise.defer = Promise.deferred = function () {
  let dtd = {};
  dtd.promise = new Promise((resolve, reject) => {
    dtd.resolve = resolve;
    dtd.reject = reject;
  })
  return dtd;
}

复制代码

Promise系列 ---想体系了解 推荐阅读这个博主的系列

参考链接

面试官:“你能手写一个 Promise 吗”

猜你喜欢

转载自juejin.im/post/7086388129174650887
今日推荐