返回

浏览器揭密异步:Promise 初探

前端

浏览器运行机制的复杂性与 JS 异步编程息息相关。我们先从浏览器多线程与 JS 单线程的对比开始,理解它们如何共存。接着,我们将深入微任务与宏任务的世界,发掘它们对 JS 异步代码执行顺序的影响。最后,我们将聚焦 JS Promise,从原理到实践,全面解析这一异步编程利器。

多线程浏览器与单线程 JavaScript

现代浏览器往往是多线程的,以便更好地利用多核 CPU 的计算能力。浏览器通常拥有多个线程,包括主线程、网络线程、渲染线程等。其中,主线程负责执行 JavaScript 代码、处理用户输入、更新 DOM 等。其他线程负责网络请求、页面渲染等任务。

然而,JavaScript 却是单线程的,这意味着在同一时刻,主线程只能执行一个任务。当主线程执行一个任务时,其他任务必须等待。这一特性既有优势也有劣势。优势在于,它简化了编程模型,避免了多线程编程中的各种并发问题。劣势在于,当一个任务执行时间过长时,整个浏览器都会卡顿。

微任务与宏任务

为了解决单线程带来的性能问题,浏览器引入了微任务和宏任务的概念。微任务是指那些需要立即执行的任务,而宏任务是指那些可以稍后执行的任务。浏览器会在执行完所有微任务之后再执行宏任务。

常见的微任务包括:

  • Promise 的 then() 和 catch() 回调函数
  • MutationObserver 的回调函数
  • DOM 事件的回调函数

常见的宏任务包括:

  • setTimeout() 和 setInterval() 的回调函数
  • requestAnimationFrame() 的回调函数
  • I/O 操作的回调函数

JavaScript Promise

Promise 是 JavaScript 中用于处理异步操作的利器。它是一个对象,代表一个异步操作的结果。Promise 的状态可以是 pending、fulfilled 或 rejected。

  • pending:表示异步操作尚未完成。
  • fulfilled:表示异步操作已成功完成,并有结果返回。
  • rejected:表示异步操作已失败,并有错误信息返回。

我们可以通过 then() 方法来监听 Promise 的状态变化。then() 方法接收两个参数,第一个参数是 fulfilled 状态的回调函数,第二个参数是 rejected 状态的回调函数。当 Promise 的状态变为 fulfilled 时,第一个回调函数会被调用,并传入 Promise 的结果。当 Promise 的状态变为 rejected 时,第二个回调函数会被调用,并传入 Promise 的错误信息。

手写 Promise

我们可以使用 ES6 的 class 来实现一个简化版的 Promise。代码如下:

class Promise {
  constructor(executor) {
    this.state = 'pending';
    this.result = undefined;
    this.error = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (result) => {
      if (this.state !== 'pending') return;

      this.state = 'fulfilled';
      this.result = result;
      this.onFulfilledCallbacks.forEach(callback => callback(result));
    };

    const reject = (error) => {
      if (this.state !== 'pending') return;

      this.state = 'rejected';
      this.error = error;
      this.onRejectedCallbacks.forEach(callback => callback(error));
    };

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

  then(onFulfilled, onRejected) {
    return new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const result = onFulfilled(this.result);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const error = onRejected(this.error);
            resolve(error);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onFulfilled(this.result);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const error = onRejected(this.error);
              resolve(error);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });
  }
}

总结

浏览器异步编程是前端开发中必不可少的一环。通过对浏览器运行机制、微任务与宏任务、以及 JS Promise 的深入理解,我们可以更加娴熟地掌握异步编程技巧,编写出更加高效、健壮的代码。希望本文能帮助您更好地理解这些概念,并在实际开发中灵活运用。