JavaScript 自己实现 Promise

简介: JavaScript 自己实现 Promise

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

描述

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

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

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

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

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

Promise 的链式调用

我们可以用 promise.then()promise.catch()promise.finally() 这些方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。这些方法还会返回一个新生成的 promise 对象,这个对象可以被非强制性的用来做链式调用。

const myPromise = (new Promise(myExecutorFunc))
  .then(handleFullfilledA, handleRejectedA)
  .then(handleFullfilledB, handleRejectedB)
  .then(handleFullfilledC, handleRejectedC);

// 或许更好的写法

const myPromise = (new Promise(myExecutorFunc))
  .then(handleFullfilledA)
  .then(handleFullfilledB)
  .then(handleFullfilledC)
  .catch(handleRejectedAny);

过早地处理被拒绝的 promise 会对之后 promise 的链式调用造成影响。不过有时候我们因为需要马上处理一个错误也只能这样做。另一方面,在没有迫切需要的情况下,可以在最后一个.catch() 语句时再进行错误处理,这种做法更加简单。

构造函数

Promise()

创建一个新的 Promise 对象。该构造函数主要用于包装还没有添加 promise 支持的函数。

静态方法

Promise.all(iterable)

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

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 对象是由关键字 new 及其构造函数来创建的。该构造函数会把一个叫做“处理器函数”(executor function)的函数作为它的参数。这个“处理器函数”接受两个函数——resolve 和 reject ——作为其参数。当异步任务顺利完成且返回结果值时,会调用 resolve 函数;而当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用reject 函数。

const myFirstPromise = new Promise((resolve, reject) => {
  // 做一些异步操作,最终会调用下面两者之一
  // resolve('Stuff worked!'); // fullfilled
  // or 
  // reject(Error('It broke')); // rejected
})

想要让某个函数拥有 promise 功能,只需要让他返回一个 promise 即可

function myAsyncFunction (url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
}

示例

let myFirstPromise = new Promise((resolve, reject) => {
  // 当异步代码执行成功时调用 resolve() ,失败时调用 reject()
  setTimeout(() => {
    resolve('Success!');
  }, 250);
});

myFirstPromise.then((successMessage) => {
  // successMessage 是 resolve() 的第一个参数
  // successMessage 参数不一定是字符串,可能是其他类型
  console.log('Yay! ' + successMessage);
});

实现

实现 resolve

要点:

  • 传参为一个 Promise 对象, 则直接返回它
  • 传参为一个 thenable 对象,返回的 Promise 对象会跟随这个对象,采用他的最终状态作为自己的状态
  • 其他情况,直接返回以该值为成功状态的 promise 对象
Promise.resolve = (param) => {
  if (param instanceof Promise) {
    return param;
  }
  return new Promise((resolve, reject) => {
    if (param && param.then && typeof param.then === 'function') {
      // param 状态变为成功会调用 resolve,将新的 Promise 的状态变为成功,反之亦然
      param.then(resolve, reject);
    } else {
      resolve(param);
    }
  });
};

实现 reject

Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传

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

实现 finally

无论当前 Promise 是成功还是失败,调用 finally 之后都会执行 finally 中的函数,并且将原封不动地往下传

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

实现 all

要点:

  • 传入参数为一个空的可迭代对象时,直接进行 resolve
  • 如果参数中有一个 promise 失败,那么 Promise.all 返回的 promise 对象失败
  • 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let result = [];
    let index = 0;
    let length = promises.length;
    if (length === 0) {
      resolve(result);
      return;
    }

    for (let i = 0; i < length; i++) {
      // promises[i] 可能不是一个 promise 对象,所以要先进行一次 `resolve`
      Promise.resolve(promises[i]).then(data => {
        result[i] = data;
        index++;
        // 所有的都成功
        if (index === length) {
          resolve(result);
        }
      }).catch(err => {
        // 任一个失败
        reject(err);
      });
  });
};

实现 allsettled

接受的结果与入参时的 promise 实例一一对应,且结果的每一项都是一个对象,通知结果和值,对象内都有一个属性 status,用来明确知道对应的这个 promise 实例的状态(fullfilled 或 rejected), fullfilled 时,对象有 value 属性,rejected 时,对象有 reason 属性,对应两种状态的返回值

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(results => {
  console.log(results);
});
// [
//   { status: 'fulfilled', value: 42 },
//   { status: 'rejected', reason: -1 }
// ]

不论接受入参的 promise 本身的状态,会返回所有 promise 的结果。

function allSettled (iterable) {
  return new Promise((resolve, reject) => {
    function addElementToResult (i, elem) {
      result[i] = elem;
      elementCount++;
      if (elementCount === result.length) {
        resolve(result);
      }
    }
    let index = 0;
    for (const promise of iterable) {
      const currentIndex = index;
      promise.then(
        (value) => addElementToResult(currentIndex, { status: 'fulfilled', value }),
        (reason) => addElementToResult(currentIndex, { status: 'rejected', reason })
      );
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    const result = new Array(index);
  });
}

实现 race

只要有一个 promise 执行完,直接 resolve 并停止执行

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    let len = promises.length;
    if (len === 0) return;
    for (let i = 0; i < len; i++) {
      Promise.resolve(promises[i]).then(data => {
        resolve(data);
        return;
      }).catch(err => {
        reject(err);
        return;
      });
    }
  });
};

实现简版 Promise

function myPromise (constructor) {
  let self = this;
  self.status = 'pending'; // pending, fulfilled, rejected
  self.value = undefined;
  self.reason = undefined;

  function resolve (value) {
    if (self.status === 'pending') {
      self.status = 'fulfilled';
      self.value = value;
    }
  }

  function reject (reason) {
    if (self.status === 'pending') {
      self.status = 'rejected';
      self.reason = reason;
    }
  }

  try {
    constructor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

myPromise.prototype.then = function (onFullfilled, onRejected) {
  let self = this;
  switch (self.status) {
    case 'fulfilled':
      onFullfilled(self.value);
      break;
    case 'rejected':
      onRejected(self.reason);
      break;
    default:
      break;
  }
};

var p = new myPromise(function (resolve, reject) {
  resolve(1);
});

p.then(function (value) {
  console.log(value);
});
// expected output: 1

使用 class 实现

class myPromise {
  constructor (fn) {
    this.resolvedCallbacks = [];
    this.rejectedCallbacks = [];

    this.status = 'PENDING';
    this.value = undefined;

    fn(this.resolve.bind(this), this.reject.bind(this));
  }

  resolve (value) {
    if (this.status === 'PENDING') {
      this.status = 'FULFILLED';
      this.value = value;

      this.resolvedCallbacks.map(cb => cb(value));
    }
  }

  reject (reason) {
    if (this.status === 'PENDING') {
      this.status = 'REJECTED';
      this.value = reason;

      this.rejectedCallbacks.map(cb => cb(reason));
    }
  }

  then (onFullfilled, onRejected) {
    if (this.status === 'FULFILLED') {
      onFullfilled(this.value);
    } else if (this.status === 'REJECTED') {
      onRejected(this.reason);
    } else {
      this.resolvedCallbacks.push(onFullfilled);
      this.rejectedCallbacks.push(onRejected);
    }
  }
}

Promise 详细实现

  • 可以把 Promise 看成一个状态机。初始时是 pending 状态,可以通过函数 resolvereject 将状态转变为 resolvedrejected。状态一旦改变,就不能再变化了。
  • then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同的实例的话,多个 then 调用就失去意义了。
  • 对于 then 来说,本质上可以把它看成是 flatMap
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

/**
 * Promise 接收一个函数参数,该函数会立刻执行
 * @param {Function} fn
 */
function MyPromise (fn) {
  let _this = this;
  _this.currentState = PENDING;
  _this.value = undefined;

  // 用于保存 then 中的回调,只有当 promise 状态为 pending 时才会缓存,且每个实例最多缓存一个
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  this.resolve = function (value) {
    if (value instanceof MyPromise) {
      // 如果 value 是个 Promise,递归执行
      return value.then(_this.resolve, _this.reject);
    }
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.map(cb => cb(value));
      }
    });
  };

  this.reject = function (reason) {
    setTimeout(() => { // 异步执行,保证执行顺序
      if (_this.currentState === PENDING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.map(cb => cb(reason));
      }
    });
  };
  
  // 用于解决 new Promise(() => throw Error('error')) 的问题
  try {
    fn(_this.resolve, _this.reject);
  } catch (error) {
    _this._reject(error);
  }
}

MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this;
  var promise2; // then 必须返回一个新的 Promise 实例

  // 如果类型不是函数需要忽略,同时也实现了透传
  onResolved = typeof onResolved === 'function' ? onResolved : value => value;
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise((resolve, reject) => {
      // 保证 onResolved 和 onRejected 异步执行
      setTimeout(function () {
        try {
         var x = onResolved(self.value);
         resolutionProcedure(promise2, x, resolve, reject);) 
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise((resolve, reject) => {
      setTimeout(function () {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise((resolve, reject) => {
      self.resolvedCallbacks.push(() => {
        try {
          var x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
      self.rejectedCallbacks.push(() => {
        try {
          var x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }
};

function resolutionProcedure (promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  // 如果 x 是个 Promise,状态为 pending 时需要等待,状态为 resolved 或 rejected 时直接执行
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      x.then(function (value) {
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject);
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  // reject 或者 resolve 其中一个执行过的话,忽略其他的
  let called = false;
  // x 是个对象或者函数
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 如果不能取出 then,则直接 reject
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolutionProcedure(promise2, y, resolve, reject);
          },
          e => {
            if (called) return;
            called = true;
            reject(e);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
相关文章
|
3天前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
16 1
|
3天前
|
前端开发 JavaScript
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
在JavaScript中,什么是promise、怎么使用promise、怎么手写promise
26 4
|
3天前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:Promise 和 Async/Await
在现代的 JavaScript 开发中,异步编程是至关重要的。本文将介绍 JavaScript 中的异步编程概念,重点讨论 Promise 和 Async/Await 这两种常见的处理异步操作的方法。通过本文的阐述,读者将能够更好地理解和应用这些技术,提高自己在 JavaScript 开发中处理异步任务的能力。
|
3天前
|
JSON 前端开发 JavaScript
【JavaScript技术专栏】JavaScript异步编程:Promise、async/await解析
【4月更文挑战第30天】JavaScript中的异步编程通过Promise和async/await来解决回调地狱问题。Promise代表可能完成或拒绝的异步操作,有pending、fulfilled和rejected三种状态。它支持链式调用和Promise.all()、Promise.race()等方法。async/await是ES8引入的语法糖,允许异步代码以同步风格编写,提高可读性和可维护性。两者结合使用能更高效地处理非阻塞操作。
|
1天前
|
前端开发 JavaScript
前端 js 经典:Promise
前端 js 经典:Promise
8 1
|
3天前
|
前端开发 JavaScript
在JavaScript中,回调函数、Promise和async/await这三种异步处理机制的优缺点
JavaScript的异步处理包括回调函数、Promise和async/await。回调函数简单易懂,但可能导致回调地狱和错误处理困难。Promise通过链式调用改善了这一情况,但仍有回调函数需求和学习成本。async/await提供同步风格代码,增强可读性和错误处理,但需ES8支持,不适用于并发执行。根据项目需求选择合适机制。
|
3天前
|
前端开发 JavaScript
js开发:请解释Promise是什么,以及它如何解决回调地狱(callback hell)问题。
Promise是JavaScript解决异步操作回调地狱的工具,代表未来可能完成的值。传统的回调函数嵌套导致代码难以维护,而Promise通过链式调用`.then()`和`.catch()`使异步流程清晰扁平。每个异步操作封装为Promise,成功时`.then()`传递结果,出错时`.catch()`捕获异常。ES6的`async/await`进一步简化Promise的使用,使异步代码更接近同步风格。
22 1
|
3天前
|
前端开发 JavaScript API
JavaScript学习笔记(一)promise与async
JavaScript学习笔记(一)promise与async
|
3天前
|
前端开发 JavaScript UED
JavaScript中的异步编程和Promise
【2月更文挑战第3天】在Web开发中,JavaScript是一门非常重要的编程语言,而异步编程是JavaScript中的一个关键概念。本文将介绍JavaScript中的异步编程特点,以及如何使用Promise来更加优雅地处理异步操作,帮助开发者更好地理解和应用这一技术。
19 3
|
3天前
|
前端开发 JavaScript 数据处理
JavaScript中的异步编程及Promise对象
【2月更文挑战第3天】 传统的JavaScript编程模式在处理异步任务时常常会导致回调地狱和代码可读性较差的问题,而Promise对象的引入为解决这一问题提供了一种优雅的解决方案。本文将介绍JavaScript中的异步编程方式以及Promise对象的使用方法和优势,帮助读者更好地理解和运用异步编程技术。
22 8