Nodejs 知识体系(三): BFF 和 事件循环

612 阅读4分钟

BFF 架构

Backend for Frontend 专门为前端服务的后端。

BFF(Backend for Frontend)是一种架构模式,专为应对现代前端应用中复杂的需求而设计。它的核心理念是为每一个前端界面(通常是不同类型的客户端,如移动端、Web 端、桌面端等)创建一个专属的后端服务。这种专属后端将根据前端的具体需求进行优化,从而避免传统架构中一个通用 API 服务无法很好地适应多种前端场景的问题。

BFF(Backend for Frontend)架构 的出现背景是为了应对现代应用程序中多样化的前端需求和复杂的数据处理挑战。随着前后端分离的普及,传统的后端服务往往难以满足不同前端(如 Web、移动端)所需的定制化数据和功能。BFF 架构通过引入专门为每种前端提供定制化数据处理和接口的中间层,优化了前后端之间的通信,简化了前端与后端微服务的交互,同时提高了性能和用户体验。它还促进了团队的协作,使前端和后端的工作更加解耦,适应不断变化的技术和业务需求。

简单来说就是传统中小型 server 使用的是 server modal controller 架构, 后端直接给前端提供 API。

随着业务体量的增加, 中台, DDD领域驱动, 数据各种 pack , 存在很多不符合前端业务的数据, 可能需要整合接口、整合数据, 于是加一层 BFF 前端自己去处理, 前端团队负责,自主取数,减少跨团队沟通。增加一层永远是解耦的大招。

事件循环

Node.js 之所以能在单线程环境中高效处理大量 I/O 操作,核心在于其 事件循环机制。事件循环是 Node.js 的运行时基础,它负责管理异步操作的执行顺序,并确保非阻塞 I/O 能在单线程中并发处理。

事件循环本质上是一个循环调度系统,它将任务分为多个阶段处理,从接收请求到执行回调,依次处理异步任务。每个阶段会处理一类特定的任务,并在完成之后执行所有挂起的微任务(如 process.nextTick() 和 Promise.then())。

了解 Node.js 事件循环的工作原理,有助于编写更高效、可扩展的应用程序,避免常见的性能瓶颈和阻塞问题。

优先级的基本原则

  1. 同步任务:所有的同步代码(直接执行的代码)首先执行,并在主线程运行。这些任务会先执行完,才会进入事件循环。
  2. 事件循环的各个阶段
  • 事件循环分为多个阶段,每个阶段负责处理不同类型的任务,如计时器、I/O 回调、setImmediate 等。
  • 每个阶段执行完所有任务后,都会检查微任务队列并执行所有微任务,才会进入下一个阶段。
  1. 微任务优先于宏任务
  • 在每个事件循环阶段执行完所有宏任务之后,都会先执行微任务队列(包括 process.nextTick() 和 Promise 的回调),然后才进入下一个阶段。
  • process.nextTick() 队列会被认为比其他微任务更重要,永远在所有微任务之前执行。

宏任务与微任务的执行顺序

宏任务(Macrotasks):

  • 包括 setTimeout()、setInterval()、setImmediate()、I/O 操作、UI 渲染等任务。
  • 宏任务会在事件循环的每个阶段处理对应的任务,执行完当前阶段后进入下一个阶段。

微任务(Microtasks):

  • 包括 process.nextTick() 和 Promise.then(),属于微任务队列。
  • 微任务队列会在每个宏任务阶段执行完毕后被清空。

执行顺序总结

  1. 执行所有 同步任务
  2. 执行 微任务队列(优先处理 process.nextTick(),再处理 Promise.then() 和其他微任务)。
  3. 进入事件循环的第一个阶段,处理 宏任务
  • 注意: setTimeout / setInterval 与 setImmediate 是轮流执行的
  1. 每个宏任务阶段结束后,再次处理 微任务队列
  2. 重复上述过程,直到所有任务都执行完毕。
async  function  async1() {
  console.log('async1 start'); // 同步任务 - 2
 await  async2(); // 进入执行栈
console.log('async end'); // 微任务 - 2
}

async  function  async2() {
  console.log('async2'); // 同步任务 - 3
}

console.log('script start.'); // 同步任务 - 1

// 宏任务 - 1
setTimeout(() => {
  console.log('setTimeout0');
  // 宏任务 - 3
 setTimeout(() => {
    console.log('setTimeout1');
  }, 0);
  // 宏任务 - 2
 setImmediate(() => {
    console.log('setImmediate');
  });
}, 0);

async1(); // 进入执行栈

// 微任务 - 1
process.nextTick(() => {
  console.log('nextTick');
});

new  Promise((resolve, reject) => {
  console.log('promise1'); // 同步任务 - 4
resolve();
  console.log('promise2'); // 同步任务 - 5
}).then(() => {
  console.log('promise.then'); // 微任务 - 3
});

console.log('script end.'); // 同步任务 - 6

执行结果

script start.
async1 start
async2
promise1
promise2
script end.
nextTick
async end
promise.then
setTimeout0
setImmediate
setTimeout1

版本变化对执行顺序的影响

在某些早期版本的 Node.js 中,Promise 微任务的优先级在事件循环中处理时出现了不一致的行为,可能导致 Promise.then() 先于 process.nextTick() 执行。但后来,Node.js 更新了事件循环和微任务处理的机制,确保了 process.nextTick() 始终优先于 Promise.then() 执行。

处理方法

  • 查看 nodejs 版本号是否是近期的新版本
console.log(`Node.js version: ${process.version}`);
  • 如果用 webstorm run code 检查 webstorm 配置的 node 版本
  • 安装 @types/node