前端编程的异步解决方案有哪些?

简介: 本文首发于微信公众号“前端徐徐”,介绍了异步编程的背景和几种常见方案,包括回调、事件监听、发布订阅、Promise、Generator、async/await和响应式编程。每种方案都有详细的例子和优缺点分析,帮助开发者根据具体需求选择最合适的异步编程方式。

本文首发微信公众号:前端徐徐。

为什么会有异步编程

首先Javascript语言的执行环境是"单线程"。就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。在这种情况下,如果中途出现一个非常耗时的任务,比如读取文件、进行网络请求、处理用户输入等就会导致整个程序等待,在这些等待的过程中,程序会被阻塞,无法进行其他操作,导致用户界面卡死或程序变得非常慢。为了解决这个问题,引入了异步编程的概念。

异步编程允许程序在等待某些操作完成时继续执行其他任务,而不是一直等待阻塞。在这种模式下,可以发起一个操作,然后继续执行其他任务,当操作完成后,通过回调函数或者类似的机制来处理操作的结果。

主要原因和需求如下:

  1. 避免阻塞:异步编程允许程序在等待耗时的操作时继续响应其他任务,提高程序的并发性和响应性能。
  2. 提高性能:对于耗时的操作(例如网络请求、文件读写等),异步编程可以允许同时执行多个操作,从而提高整体性能。
  3. 用户体验:在前端应用中,异步编程非常重要,可以确保用户界面在进行耗时操作时不会被阻塞,保持流畅的交互体验。
  4. 并发编程:在服务器端或多线程环境中,异步编程也是必要的,可以提高系统的吞吐量和并发性能。

异步编程的几种方案

回调

回调函数曾经是 JavaScript 中实现异步函数的主要方式。

例子

function doStep1(init, callback) {
  const result = init + 1;
  callback(result);
}
function doStep2(init, callback) {
  const result = init + 2;
  callback(result);
}
function doStep3(init, callback) {
  const result = init + 3;
  callback(result);
}
function doOperation() {
  doStep1(0, result1 => {
    doStep2(result1, result2 => {
      doStep3(result2, result3 => {
        console.log(`结果:${result3}`);
      });
    });
  });
}
doOperation();

优缺点

优点:简单易懂,是一种传统的异步编程方式。

缺点:容易导致回调地狱,代码嵌套过深,可读性和维护性差。面对这样的嵌套回调,处理错误也会变得非常困难:你必须在“金字塔”的每一级处理错误,而不是在最高一级一次完成错误处理。

事件监听

主要是取决于事件的发生,有事件发生,对应事件绑定的函数就会执行。

例子

element.addEventListener("click", function(){ 
  alert("Hello World!"); 
});

优缺点

优点:可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。

缺点:整个程序都要变成事件驱动型,运行流程会变得很不清晰。

发布订阅

前端发布订阅模式(Pub/Sub)是一种常见的设计模式,用于实现组件之间的解耦和事件通信,在异步处理的场景中应用也非常广泛。

例子

// 发布订阅管理器
const pubSub = {
  events: {},
  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  },
  publish(event, data) {
    if (!this.events[event]) return;
    this.events[event].forEach(callback => callback(data));
  },
};
// 按钮组件
<button id="btn">点击我</button>
const button = document.getElementById('btn');
button.addEventListener('click', () => {
  // 发布按钮点击事件,传递消息
  pubSub.publish('buttonClicked', '按钮被点击了!');
});
// 消息组件
<div id="message"></div>
// 消息显示组件
const messageElement = document.getElementById('message');
// 订阅按钮点击事件
pubSub.subscribe('buttonClicked', message => {
  // 显示接收到的消息
  messageElement.textContent = message;
});

在这个例子中,我们通过pubSub对象实现了一个简单的发布订阅管理器。在按钮组件中,当按钮被点击时,我们通过pubSub.publish方法发布了一个名为buttonClicked的事件,并传递了相应的消息。在消息显示组件中,我们通过pubSub.subscribe方法订阅了buttonClicked事件,并提供了一个回调函数来处理接收到的消息。当按钮被点击时,消息显示组件会接收到通知,并将消息显示在页面上。

优缺点

优点:

  1. 解耦:发布订阅模式可以将组件解耦,使它们不需要直接知道彼此的存在。这样,当一个组件发生改变时,不会影响其他组件的功能,提高了代码的灵活性和可维护性。
  2. 可扩展性:由于组件之间的通信通过发布订阅模式实现,新的组件可以很容易地加入到系统中,无需修改现有代码。
  3. 异步处理:发布订阅模式适用于异步事件处理,可以在某个事件发生时通知所有订阅者进行相应的处理。
  4. 事件中心:发布订阅模式提供了一个事件中心,方便管理和维护不同事件及其对应的订阅者。

缺点:

  1. 内存管理:如果不适当地使用发布订阅模式,可能会导致内存泄漏。因为发布订阅模式需要维护事件订阅列表,如果订阅者没有正确地进行取消订阅操作,可能会导致订阅者一直存在于内存中,无法被回收。
  2. 可读性:使用发布订阅模式可能会导致代码逻辑变得复杂,特别是在多个组件之间存在复杂的事件关系时,代码可能会比较难以理解和维护。
  3. 不适合所有场景:并不是所有的应用场景都适合使用发布订阅模式。在简单的应用中,使用发布订阅模式可能会增加不必要的复杂性,导致代码冗余。
  4. 调试困难:由于发布订阅模式是通过事件来通信的,当出现问题时,可能需要跟踪事件的传递过程,对于复杂的应用可能会增加调试的难度。

Promise

Promise 是现代 JavaScript 中异步编程的基础,是一个由异步函数返回的可以向我们指示当前操作所处的状态的对象。

例子

const fetchPromise = fetch('https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/products.json');
console.log(fetchPromise);
fetchPromise.then( response => {
  console.log(`已收到响应:${response.status}`);
});
console.log("已发送请求……");

优缺点

优点:状态改变就不会再变,任何时候都能得到相同的结果。将异步事件的处理流程化,写法更方便。

缺点:仍然需要通过.then()和.catch()方法来处理异步操作,可能会有一些回调嵌套。

Generator

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。

例子

function* foo(index) {
  while (index < 2) {
    yield index;
    index++;
  }
}
const iterator = foo(0);
console.log(iterator.next().value);
// Expected output: 0
console.log(iterator.next().value);
// Expected output: 1

优缺点

优点:Generator函数可以在函数执行的不同阶段返回值,结合Promise可以实现更复杂的异步流程控制。

缺点:相对于其他方案,Generator函数语法较复杂,不太直观。

async/await

async 和 await 关键字是最近添加到JavaScript语言里面的。它们是ECMAScript 2017 的一部分,简单来说,它们是基于promises的语法糖,使异步代码更易于编写和阅读。通过使用它们,异步代码看起来更像是老式同步代码。

例子

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}
async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  // Expected output: "resolved"
}
asyncCall();

优缺点

优点:使用async/await可以让异步代码看起来更像同步代码,可读性更好。

缺点:需要在支持ES6的环境下运行,对于旧的浏览器可能需要进行转译。

响应式编程

响应式编程,它是一种基于事件的模型。在上面的异步编程模式中,我们描述了两种获得上一个任务执行结果的方式,一个就是主动轮训,我们把它称为 Proactive 方式。另一个就是被动接收反馈,我们称为 Reactive。简单来说,在 Reactive 方式中,上一个任务的结果的反馈就是一个事件,这个事件的到来将会触发下一个任务的执行。

例子

我们用RxJS库来给出一个简单的例子。

在HTML文件中,添加一个按钮和一个用于显示结果的元素:

<button id="btn">点击我</button>
<p id="result"></p>

在JavaScript文件中,使用RxJS来创建Observables并订阅它:

// 引入RxJS库
import { fromEvent } from 'rxjs';
// 获取按钮和结果元素
const button = document.getElementById('btn');
const resultElement = document.getElementById('result');
// 创建一个点击事件的Observables
const clickObservable = fromEvent(button, 'click');
// 订阅Observables,当按钮点击时触发回调函数
clickObservable.subscribe(() => {
  resultElement.textContent = '按钮被点击了!';
});

在上面的例子中,我们使用RxJS的fromEvent函数来创建一个点击事件的Observables。然后,我们使用subscribe方法来订阅这个Observables,并传入一个回调函数。当按钮被点击时,回调函数会执行,将文本内容更新为"按钮被点击了!"。

优缺点

优点:RxJS基于Observables序列,可以更方便地处理事件、处理异步数据以及构建复杂的数据流,非常适合处理实时数据和事件驱动的应用。

缺点:学习曲线较陡峭,对于简单的应用可能会显得过于复杂。

总结

综合考虑,对于简单的异步操作,Promise和async/await是较为常用和简洁的解决方案。而对于复杂的异步数据流和事件处理,RxJS提供了更强大的工具和抽象能力。Generator函数在一些特定的场景中也可以发挥一定作用,但使用较少。回调函数在现代前端开发中使用较少,更多地被其他方案所替代。

在选择合适的异步编程方案时,需根据具体项目需求、团队熟悉程度和项目规模等因素进行权衡和取舍。

相关文章
|
1天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
3天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1540 5
|
1月前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
7天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
574 22
|
3天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
201 3
|
10天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
10天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
571 5
|
23天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
6天前
|
XML 安全 Java
【Maven】依赖管理,Maven仓库,Maven核心功能
【Maven】依赖管理,Maven仓库,Maven核心功能
233 3
|
9天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
327 2