前端面试八股文:单线程的JavaScript是如何实现异步的

JavaScript 的单线程与异步机制

JavaScript 是单线程语言,意味着同一时间只能执行一个任务。异步机制通过事件循环(Event Loop)、任务队列和 Web APIs 协作实现,避免阻塞主线程。

事件循环(Event Loop)

事件循环是 JavaScript 异步的核心机制。它不断检查调用栈(Call Stack)和任务队列(Task Queue),当调用栈为空时,将队列中的任务推入调用栈执行。

  • 调用栈:存储同步任务的执行上下文,后进先出(LIFO)。
  • 任务队列:存储异步任务的回调函数,分为宏任务(MacroTask)和微任务(MicroTask)。

Web APIs 与异步任务

浏览器或 Node.js 提供的 Web APIs(如 setTimeoutXMLHttpRequest)在异步操作完成后,将回调函数放入任务队列。

  • 宏任务 :包括 setTimeoutsetInterval、I/O 操作等,由事件循环按顺序处理。
  • 微任务 :包括 Promise.thenMutationObserver,在宏任务执行结束后立即执行,优先级高于宏任务。

示例流程

  1. 同步代码执行,遇到异步任务(如 setTimeout)时,交给 Web API 处理。
  2. Web API 在异步操作完成后,将回调函数放入任务队列。
  3. 调用栈为空时,事件循环从任务队列中取出回调函数执行。

代码示例

javascript 复制代码
console.log("Start"); // 同步任务

setTimeout(() => {
  console.log("Timeout"); // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log("Promise"); // 微任务
});

console.log("End"); // 同步任务

输出顺序

复制代码
Start
End
Promise
Timeout

关键点

  • 微任务(如 Promise)优先于宏任务(如 setTimeout)执行。
  • 事件循环确保异步任务不阻塞主线程,通过队列机制有序处理回调。

这种机制使得单线程的 JavaScript 能够高效处理并发操作。


Promise 的实现原理

Promise 是 JavaScript 中用于处理异步操作的一种机制,其核心原理基于状态机、回调函数队列和链式调用。以下是其实现原理的关键点:

状态机

Promise 有三种状态:pending(等待)、fulfilled(完成)和 rejected(拒绝)。状态一旦从 pending 转变为 fulfilledrejected,就不可再改变。这种状态机设计确保了 Promise 的不可逆性。

构造函数

Promise 构造函数接收一个执行器函数(executor),该函数会立即执行。执行器函数包含两个参数:resolvereject,用于改变 Promise 的状态并传递结果或错误。

javascript 复制代码
class MyPromise {
  constructor(executor) {
    this.state = 'pending';
    this.value = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach(cb => cb());
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.value = reason;
        this.onRejectedCallbacks.forEach(cb => cb());
      }
    };

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

then 方法用于注册回调函数,接收 onFulfilledonRejected 两个参数。根据当前状态,立即执行回调或将回调存入队列(若状态为 pending)。then 返回一个新的 Promise,支持链式调用。

javascript 复制代码
then(onFulfilled, onRejected) {
  const newPromise = new MyPromise((resolve, reject) => {
    const handleFulfilled = () => {
      try {
        if (typeof onFulfilled === 'function') {
          const result = onFulfilled(this.value);
          resolve(result);
        } else {
          resolve(this.value);
        }
      } catch (error) {
        reject(error);
      }
    };

    const handleRejected = () => {
      try {
        if (typeof onRejected === 'function') {
          const result = onRejected(this.value);
          resolve(result);
        } else {
          reject(this.value);
        }
      } catch (error) {
        reject(error);
      }
    };

    if (this.state === 'fulfilled') {
      setTimeout(handleFulfilled, 0);
    } else if (this.state === 'rejected') {
      setTimeout(handleRejected, 0);
    } else {
      this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
      this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
    }
  });

  return newPromise;
}
异步调度

回调函数通过 setTimeout 或微任务(如 queueMicrotask)异步执行,确保 then 方法总是在当前执行栈结束后运行,符合 Promises/A+ 规范。

链式调用与值穿透

then 的参数不是函数,则直接将值传递给下一个 Promise。若回调返回一个 Promise,则会等待其状态改变后再继续链式调用。

javascript 复制代码
const promise = new MyPromise((resolve) => resolve(1))
  .then(val => val + 1)
  .then(val => console.log(val)); // 输出 2
错误处理

通过 try/catch 捕获执行器或回调中的错误,并调用 reject 方法。catch 方法本质上是 then(null, onRejected) 的语法糖。

总结

Promise 的核心是通过状态管理、回调队列和链式调用实现异步操作的顺序控制。其设计确保了代码的可读性和可维护性,避免了回调地狱(Callback Hell)。

相关推荐
AKA__老方丈12 小时前
vue-cropper图片裁剪、旋转、缩放、实时预览
前端·vue.js
老鼠只爱大米13 小时前
LeetCode经典算法面试题 #739:每日温度(单调栈、动态规划等多种实现方案详解)
算法·leetcode·面试·动态规划·单调栈·每日温度
老鼠只爱大米13 小时前
LeetCode经典算法面试题 #394:字符串解码(递归、双栈、迭代构建等五种实现方案详解)
算法·leetcode·面试·递归··字符串解码
梦65013 小时前
Vue 单页面应用 (SPA) 与 多页面应用 (MPA) 对比
前端·javascript·vue.js
清铎13 小时前
大模型训练_week3_day15_Llama概念_《穷途末路》
前端·javascript·人工智能·深度学习·自然语言处理·easyui
岛泪14 小时前
把 el-cascader 的 options 平铺为一维数组(只要叶子节点)
前端·javascript·vue.js
lendsomething14 小时前
graalvm使用实战:在java中执行js脚本
java·开发语言·javascript·graalvm
Kiyra14 小时前
阅读 Netty 源码关于 NioEventLoop 和 Channel 初始化部分的思考
运维·服务器·前端
Mr__Miss15 小时前
Redis网络模型
数据库·redis·面试
冰暮流星15 小时前
javascript的switch语句介绍
java·前端·javascript