返回

手撕面试官...啊,不,手撕Promise

前端

Promise详解

Promise是JavaScript中用于处理异步编程的利器,它可以让你以一种同步的方式处理异步操作的结果。为了理解Promise的原理,我们需要先了解一下JavaScript的事件循环机制。

事件循环

JavaScript的事件循环是一个单线程循环,它不断地从任务队列中取出任务并执行。任务队列是一个先进先出的队列,这意味着先进入队列的任务会先被执行。

当JavaScript代码执行时,它会将任务添加到任务队列中。当任务队列不为空时,JavaScript引擎会从队列中取出第一个任务并执行它。如果任务是一个同步任务,它会立即执行并完成。如果任务是一个异步任务,它会先被添加到一个单独的异步任务队列中,然后在适当的时候被执行。

回调函数

在JavaScript中,回调函数是指当某个异步操作完成后被调用的函数。例如,当一个HTTP请求完成后,我们可以使用回调函数来处理服务器返回的数据。

回调函数是一个非常有用的工具,但它也有一个缺点:它会导致代码难以阅读和维护。想象一下,如果你在一个程序中使用了多个异步操作,那么你的代码中就会充斥着大量的回调函数,这会让你的代码变得非常混乱。

Promise

Promise正是为了解决回调函数的缺点而诞生的。Promise是一个对象,它代表了一个异步操作的结果。当异步操作完成后,Promise会进入已完成状态,并调用注册的回调函数来处理结果。

与回调函数相比,Promise有以下优点:

  • 代码更易读、更易维护。 Promise使用链式调用来处理异步操作的结果,这使得代码更加清晰易懂。
  • 可以更好地处理错误。 Promise可以捕获异步操作中发生的错误,并将其传递给注册的回调函数。
  • 可以组合多个异步操作。 Promise可以很容易地组合多个异步操作,并以一种同步的方式处理它们的结果。

手写Promise

现在,我们已经对Promise有了基本的了解,接下来我们将手把手教你如何用JavaScript从头开始构建一个自己的Promise。

构造函数

首先,我们需要定义一个Promise的构造函数。Promise构造函数接收一个参数,该参数是一个函数,称为执行器函数(executor)。执行器函数有两个参数,分别是resolve和reject,这两个函数用于将Promise的状态从挂起状态变为已完成状态。

function Promise(executor) {
  this.state = 'pending'; // 初始状态为挂起
  this.value = undefined; // 结果值
  this.reason = undefined; // 拒绝原因
  this.onFulfilledCallbacks = []; // 存放已完成时的回调函数
  this.onRejectedCallbacks = []; // 存放已拒绝时的回调函数

  // 执行器函数立即执行,用于初始化Promise的状态
  executor(resolve, reject);
}

resolve和reject函数

resolve函数用于将Promise的状态从挂起状态变为已完成状态,并传递一个值作为结果。reject函数用于将Promise的状态从挂起状态变为已拒绝状态,并传递一个原因作为拒绝原因。

function resolve(value) {
  // 将状态从挂起变为已完成
  this.state = 'fulfilled';
  // 将结果值保存起来
  this.value = value;
  // 依次调用已完成时的回调函数
  this.onFulfilledCallbacks.forEach(callback => callback(value));
}

function reject(reason) {
  // 将状态从挂起变为已拒绝
  this.state = 'rejected';
  // 将拒绝原因保存起来
  this.reason = reason;
  // 依次调用已拒绝时的回调函数
  this.onRejectedCallbacks.forEach(callback => callback(reason));
}

then方法

then方法用于在Promise完成后执行指定的回调函数。then方法接收两个参数,分别是onFulfilled和onRejected,这两个参数都是函数,分别用于处理已完成和已拒绝的情况。

Promise.prototype.then = function(onFulfilled, onRejected) {
  // 如果没有传递onFulfilled,则使用一个默认函数
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  // 如果没有传递onRejected,则使用一个默认函数
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

  // 根据Promise的状态,将回调函数添加到相应的数组中
  if (this.state === 'fulfilled') {
    setTimeout(() => {
      onFulfilled(this.value);
    }, 0);
  } else if (this.state === 'rejected') {
    setTimeout(() => {
      onRejected(this.reason);
    }, 0);
  } else {
    // 如果Promise的状态还是挂起,则将回调函数添加到相应的数组中,等待状态改变后再执行
    this.onFulfilledCallbacks.push(onFulfilled);
    this.onRejectedCallbacks.push(onRejected);
  }

  // 返回一个新的Promise,以便可以继续链式调用
  return new Promise((resolve, reject) => {
    this.then(value => {
      try {
        const result = onFulfilled(value);
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }, reason => {
      try {
        const result = onRejected(reason);
        resolve(result);
      } catch (error) {
        reject(error);
      }
    });
  });
};

catch方法

catch方法用于处理Promise被拒绝的情况。catch方法接收一个参数,该参数是一个函数,用于处理已拒绝的情况。

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

finally方法

finally方法用于无论Promise是已完成还是已拒绝都会执行的回调函数。finally方法接收一个参数,该参数是一个函数,用于处理无论Promise是已完成还是已拒绝的情况。

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

结语

至此,我们已经手把手地实现了一个Promise。通过对Promise的深入理解和手写实现,你对异步编程和Promise有了更深入的认识。在实际开发中,你可以使用原生Promise或第三方Promise库来处理异步操作,这将使你的代码更加清晰易懂、更易维护。