处理 JavaScript 异步操作的几种方法总结

引言

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,了解一下,唯一一个高度与之持平的项目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/413268.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 事务相关及@Transactional的使用建议

使用步骤&#xff1a; 步骤一、在spring配置文件中引入<tx:>命名空间<beans xmlns"http://www.springframework.org/schema/beans" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xmlns:tx"http://www.springframework.org/schema/…

谷歌浏览器安装Vue Devtools插件(国内的谷歌浏览器如何安装插件)

分享给大家一个谷歌插件网站&#xff0c;适合国内谷歌浏览器无法安装插件的问题&#xff0c;你懂的 点击这里下载Vue.js Devtools插件&#xff0c; 喜欢的可以收藏这个插件资源网站&#xff0c;分享给大家 第一步&#xff1a;下载后解压获得CRX文件&#xff0c;如下图 第二步…

MySQL操作权限整理

用户权限管理主要有以下作用&#xff1a; 1. 可以限制用户访问哪些库、哪些表 2. 可以限制用户对哪些表执行SELECT、CREATE、DELETE、DELETE、ALTER等操作 3. 可以限制用户登录的IP或域名 4. 可以限制用户自己的权限是否可以授权给别的用户 一、用户授权 mysql> grant a…

Objective-C学习笔记-使用NSString与NSData读写文件

1.NSString读写文件 NSString *helloText"您好啊&#xff01;";NSError *error;if ([helloText writeToFile:"/tmp/test.txt" atomically:true encoding:NSUTF8StringEncoding error:&error]){NSLog("writeToFile success");}else{NSLog(&qu…

[css] 如何将元素的所有css属性恢复为初始化状态?

[css] 如何将元素的所有css属性恢复为初始化状态&#xff1f; all:upset个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

vue报错 TypeError: merge is not a function

利用ncu -u升级去年的vue项目package.json里的所有依赖&#xff0c;目的是想增删改它去做另一个项目&#xff0c; 却发生了这样一个错误&#xff1a;&#xff08;如下&#xff09; 查找问题原因&#xff1a; 这是webpack配置中区分环境配置文件中的插件webpack-merge的报错&a…

同步和异步有什么区别?

所谓同步&#xff0c;就是发出一个功能调用时&#xff0c;在没有得到结果之前&#xff0c;该调用就不返回或者继续执行后续操作。 异步跟同步相对&#xff0c;当一个异步调用发出后&#xff0c;调用者在没有得到结果之前&#xff0c;就可以继续执行后续操作。当这个调用完成后&…

Invalid options object. Copy Plugin has been initialized using an options object that does not match

报错&#xff1a; 报错文件和代码&#xff1a;查看了官网也没有看出所以然&#xff0c;最后在npm官网上找打了原因 错误配置&#xff1a; 怎么看都没有错误 最后参看一下这个npn官网找打了原因&#xff0c;地址&#xff1a;https://www.npmjs.com/package/copy-webpack-plugin …

[css] css中的baseline,你知道吗?

[css] css中的baseline&#xff0c;你知道吗&#xff1f; baseline是西文字体里面的一种定位&#xff0c;vertical-align:baseline是指行内元素里的文字&#xff0c;在垂直方向上&#xff0c;按字体的基线排列&#xff0c;基线就是可以类似我们小学写英文字母时的带线的格子&a…

Leetcode——300. 最长上升子序列

题目描述&#xff1a;题目链接 给定一个无序的整数数组&#xff0c;找到其中最长上升子序列的长度。 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101]&#xff0c;它的长度是 4。 说明: 可能会有多种最长上升子序列的组合&#xff0c;你只需…

后台返回数据打印是[object object]的,报错:SyntaxError: JSON.parse: expected property name or ‘}‘ at line 1 column

今天基于这个问题纠结了一下午&#xff0c;导致这个问题的坑也是挺深的&#xff0c;查找问题最好是从这条数据的存储开始查找 问题1&#xff1a;先确定后台接收数据后存储到数据库里有没有自动转义特殊字符&#xff0c;比如 原始数据是&#xff1a;[{"user_id":20,…

[css] 你知道什么是动态伪类吗?

[css] 你知道什么是动态伪类吗&#xff1f; 锚点伪类&#xff08;a标签&#xff09; 1、:link 未操作的链接 2、:visited 该链接已被访问&#xff0c;一旦:visited&#xff0c;:link/:active不再起作用。用户行为伪类 1、:hover 鼠标悬停该元素 2、:active 鼠标点击该元素 3、…

vue在多方法执行完后再执行另一个方法(等待请求完数据再执行)async/await使用方法和Promise.all

vue在一个方法执行完后执行另一个方法 用Promise.all来实现。 Promise是ES6的新特性&#xff0c;用于处理异步操作逻辑&#xff0c;用过给Promise添加then和catch函数&#xff0c;处理成功和失败的情况 ES7中新提出async搭配await&#xff0c;建议使用async搭配await。 func…

Java开发框架和中间件面试题(4)

27.如何自定义Spring Boot Starter&#xff1f; 1.实现功能 2.添加Properties 3.添加AutoConfiguration 4.添加spring.factory 在META INF下创建spring.factory文件 6.install 28.为什么需要spring boot maven plugin? spring boot maven plugin 提供了一些像jar一样打包…

第二周每周例行报告

1.本周PSP 类型任务开始时间结束时间间隔时间净时间准备工作复习C#&#xff0c;看书2018.9.19 17&#xff1a;032018.9.19 18&#xff1a;17 0min74min编程编写功能一2018.9.20 18&#xff1a;072018.9.20 22&#xff1a;4323min253min编程完善修改功能一2018.9.21…

[css] 使用css3画一个扇形

[css] 使用css3画一个扇形 四个半圆叠加&#xff0c;过半调整 z-index .container { width: 200px; height: 200px; position: relative; border-radius: 100%; } div { width: 50%; height: 100%; position: absolute; left: 0; top: 0; } .fan-1, .fan-3 { background: #C…

ES6 Promise 并行执行和顺序执行

1.Promise.all 并行执行promise getA和getB并行执行&#xff0c;然后输出结果。如果有一个错误&#xff0c;就抛出错误 /*** 每一个promise都必须返回resolve结果才正确* 每一个promise都不处理错误*/const getA new Promise((resolve, reject) > {//模拟异步任务setTime…

poj 1083 Moving Tables

题目 两种做法&#xff0c;开始用贪心做的&#xff0c;有种情况没考虑到&#xff0c;结果排序错了。 这个例子&#xff0c;感觉上有三个交点&#xff0c;以为是30&#xff0c;其实是20. 贪心代码&#xff1a; #include <iostream> #include <cstdio> #include <…

[css] 请使用css3来模拟中/英文打字的效果

[css] 请使用css3来模拟中/英文打字的效果 :after 加个竖条闪啊闪&#xff0c;至于文字一个个出现嘛&#xff0c;text-indent 需要知道字数&#xff0c;clip-path 需要知道宽高&#xff0c;好像也没有特别好的办法。当然如果是单行文本的话用 width overflow 也是阔以的。个人…

[css] 使用css将图片转换成黑白的效果

[css] 使用css将图片转换成黑白的效果 filter: saturate(0);个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题