引言

js的异步操作,已经是一个老生常谈的话题,关于这个话题的文章随便google一下都可以看到一大堆。处理js的异步操作,都有一些什么方法呢?仁者见仁智者见智

一、回调函数

传说中的“callback hell”就是来自回调函数。而回调函数也是最基础最常用的处理js异步操作的办法。我们来看一个简单的例子:

首先定义三个函数:

function fn1 () {console.log('Function 1')
}function fn2 () {setTimeout(() => {console.log('Function 2')}, 500)
}function fn3 () {console.log('Function 3')
}

其中fn2可以视作一个延迟了500毫秒执行的异步函数。现在我希望可以依次执行fn1fn2fn3。为了保证fn3在最后执行,我们可以把它作为fn2的回调函数:

function fn2 (f) {setTimeout(() => {console.log('Function 2')f()}, 500)
}fn2(fn3)

可以看到,fn2fn3完全耦合在一起,如果有多个类似的函数,很有可能会出现fn1(fn2(fn3(fn4(...))))这样的情况。回调地狱的坏处我就不赘述了,相信各位读者一定有自己的体会。

二、事件发布/订阅

发布/订阅模式也是诸多设计模式当中的一种,恰好这种方式可以在es5下相当优雅地处理异步操作。什么是发布/订阅呢?以上一节的例子来说,fn1fn2fn3都可以视作一个事件的发布者,只要执行它,就会发布一个事件。这个时候,我们可以通过一个事件的订阅者去批量订阅并处理这些事件,包括它们的先后顺序。下面我们基于上一章节的例子,增加一个消息订阅者的方法(为了简单起见,代码使用了es6的写法):

class AsyncFunArr {constructor (...arr) {this.funcArr = [...arr]}next () {const fn = this.funcArr.shift()if (typeof fn === 'function') fn()}run () {this.next()}
}const asyncFunArr = new AsyncFunArr(fn1, fn2, fn3)

然后在fn1fn2fn3内调用其next()方法:

function fn1 () {console.log('Function 1')asyncFunArr.next()
}function fn2 () {setTimeout(() => {console.log('Function 2')asyncFunArr.next()}, 500)
}function fn3 () {console.log('Function 3')asyncFunArr.next()
}// output =>
// Function 1
// Function 2
// Function 3

可以看到,函数的处理顺序等于传入AsyncFunArr的参数顺序。AsyncFunArr在内部维护一个数组,每一次调用next()方法都会按顺序推出数组所保存的一个对象并执行,这也是我在实际的工作中比较常用的方法。

三、Promise

使用Promise的方式,就不需要额外地编写一个消息订阅者函数了,只需要异步函数返回一个Promise即可。且看例子:

function fn1 () {console.log('Function 1')
}function fn2 () {return new Promise((resolve, reject) => {setTimeout(() => {console.log('Function 2')resolve()}, 500)})
}function fn3 () {console.log('Function 3')
}

同样的,我们定义了三个函数,其中fn2是一个返回Promise的异步函数,现在我们希望按顺序执行它们,只需要按以下方式即可:

fn1()
fn2().then(() => { fn3() })// output =>
// Function 1
// Function 2
// Function 3

使用Promise与回调有两个最大的不同,第一个是fn2fn3得以解耦;第二是把函数嵌套改为了链式调用,无论从语义还是写法上都对开发者更加友好。

四、generator

如果说Promise的使用能够化回调为链式,那么generator的办法则可以消灭那一大堆的Promise特征方法,比如一大堆的then()

function fn1 () {console.log('Function 1')
}function fn2 () {setTimeout(() => {console.log('Function 2')af.next()}, 500)
}function fn3 () {console.log('Function 3')
}function* asyncFunArr (...fn) {fn[0]()yield fn[1]()fn[2]()
}const af = asyncFunArr(fn1, fn2, fn3)af.next()// output =>
// Function 1
// Function 2
// Function 3

在这个例子中,generator函数asyncFunArr()接受一个待执行函数列表fn,异步函数将会通过yield来执行。在异步函数内,通过af.next()激活generator函数的下一步操作。

这么粗略的看起来,其实和发布/订阅模式非常相似,都是通过在异步函数内部主动调用方法,告诉订阅者去执行下一步操作。但是这种方式还是不够优雅,比如说如果有多个异步函数,那么这个generator函数肯定得改写,而且在语义化的程度来说也有一点不太直观。

五、优雅的async/await

使用最新版本的Node已经可以原生支持async/await写法了,通过各种pollyfill也能在旧的浏览器使用。那么为什么说async/await方法是最优雅的呢?且看代码:

function fn1 () {console.log('Function 1')
}function fn2 () {return new Promise((resolve, reject) => {setTimeout(() => {console.log('Function 2')resolve()}, 500)})
}function fn3 () {console.log('Function 3')
}async function asyncFunArr () {fn1()await fn2()fn3()
}asyncFunArr()// output =>
// Function 1
// Function 2
// Function 3

有没有发现,在定义异步函数fn2的时候,其内容和前文使用Promise的时候一模一样?再看执行函数asyncFunArr(),其执行的方式和使用generator的时候也非常类似。

异步的操作都返回Promise,需要顺序执行时只需要await相应的函数即可,这种方式在语义化方面非常友好,对于代码的维护也很简单——只需要返回Promise并await它就好,无需像generator那般需要自己去维护内部yield的执行。

六、尾声

作为一个归纳和总结,本文内容的诸多知识点也是来自于他人的经验,不过加入了一些自己的理解和体会。不求科普于他人,但求作为个人的积累。希望读者可以提出订正的意见,共同学习进步!

题外话:

《Pi Network 免费挖矿国外热门项目 一个π币大约值3元到10元》相信过去BTC的人,信不信未来的PI,了解一下,唯一一个高度与之持平的项目

处理 JavaScript 异步操作的几种方法总结相关推荐

  1. 分析网页 JavaScript Bundles 的几种方法

    分析你网页中的  JavaScript Bundles 大小,并限制网页中的 JavaScript 数量,可以减少浏览器花费在解析.编译和执行 JavaScript 的时间.这可以加快浏览器可以开始响 ...

  2. JavaScript创建对象的4种方法

    JavaScript 创建对象的4种方法 所谓对象就是用来帮助你完成一些事情是,对象是有特征和行为的,是具体特指的某一个事物.使用对象可以使程序更加整洁 通过Object()方法来创建 Object( ...

  3. HTML5动态加载资源方式,动态加载JavaScript文件的两种方法

    这篇文章主要为大家详细介绍了动态加载JavaScript文件的两种方法,感兴趣的小伙伴们可以参考一下 第一种便是利用ajax方式,把script文件代码从背景加载到前台,而后对加载到的内容经过eval ...

  4. html按照字数分页,纯javascript实现分页(两种方法)

    先给大家贴效果图: 网上确实有很多分页的插件以及开源代码,单本是一个后台开发猿,前台css等样式还驾驭不住,所以就开始自己去写了.其实这个分页原理很简单,就是用ajax往后台传值(当前页码),后台使用 ...

  5. php javascript分页,纯javascript实现分页(两种方法)

    有的时候页面需要很多不同的表组成的数据,该怎么分页呢?使用数据库分页很简单,那么如何使用js实现分页呢?接下来,小编帮大家解决这个问题,需要的朋友一起来学习吧 先给大家贴效果图: 网上确实有很多分页的 ...

  6. JS教程之 识别 JavaScript 数据类型:两种方法就足够了

    Primitive type原始类型: Null.Undefined.Number.String.Boolean.Symbol.BigInt vObject type 对象类型: Object 你知道 ...

  7. JavaScript数组去重6种方法

    数组去重涉及基础知识较多,总结了以下6个方法: 双重for循环,push新数组: 双重for循环,splice原数组: 单个for循环,遍历对象属性: 单个for循环,sort排序后遍历: ES5,i ...

  8. JavaScript冒泡排序的四种方法

    第一种这种方法冒泡排序 比较相邻的两个元素,如果前一个比后一个大,则交换位置. 第一轮把最大的元素放到了最后面. 由于每次排序最后一个都是最大的,所以之后按照步骤1排序最后一个元素不用比 functi ...

  9. JavaScript创建对象的两种方法和遍历对象的属性

    创建新对象有两种不同的方法: 定义并创建对象的实例 使用函数来定义对象,然后创建新的对象实例 1.定义并创建对象的实例 var person=new Object(); person.firstnam ...

最新文章

  1. PyTorch 高级实战教程:基于 BI-LSTM CRF 实现命名实体识别和中文分词
  2. mySQL笔记(1)
  3. springboot实现上传文件
  4. Atitit RSA非对称加密原理与解决方案
  5. think-in-java(17)容器深入研究
  6. 倍福嵌入式控制器PLC各型号简介
  7. 双绞线传输距离_一看就懂的网络传输介质介绍
  8. 实现裸金属服务器的安全微分段
  9. 在Sharepoint Designer 2007 中加入定制的工作流动作
  10. 京东下拉词框天猫下拉词框优化推广方法分享
  11. CentOS hadoop 伪分布式安装步骤
  12. Python设计模式:抽象工厂模式
  13. 如何快速找回丢失的数据?
  14. Java地图坐标转换
  15. 魔兽世界服务器卡 邮件寄不出去,魔兽世界怀旧服邮件收不到怎么办 WOW怀旧服邮件取不出来解决方法...
  16. 计算机无线网络计算机文件共享,无线局域网共享_在同一个无线局域网内如何共享文件?...
  17. 麦语言和python区别_放弃文华财经,自己编程实现期货程序化交易
  18. Beaglebone Black–智能家居控制系统 LAS - 用 UART 连接 ESP8266 (ESP-01 版)
  19. arm linux关机命令,嵌入式Linux的关闭命令是什么?
  20. 计算机知识怎么做框架,计算机基础知识框架.ppt

热门文章

  1. [html] 你知道什么是粘性布局吗?
  2. [vue] vue变量名如果以_、$开头的属性会发生什么问题?怎么访问到它们的值?
  3. [css] 如何形成BFC?
  4. 前端学习(2680):注意看位置 少加注释
  5. 前端学习(2025)vue之电商管理系统电商系统之渲染订单列表数据
  6. spring mvc学习(7):springmvc学习笔记(常用注解)
  7. java学习(61):适配器
  8. js获取当前时间(昨天、今天、明天)
  9. 常用的前端JavaScript方法封装(49种)
  10. 互联网大佬学历背景大揭秘,看看是你的老乡还是校友