关于js中异步问题的解决方案
在js中有一个始终无法绕过的问题,如何优雅地解决异步问题。实际上,js在执行过程中,每遇到一个异步函数,都会将这个异步函数放入一个异步队列中,只有当同步线程执行结束之后,才会开始执行异步队列中的函数,这个是讨论解决异步方案的前提。
解决问题的方法
主流的解决方法主要有以下几种:
- 回调函数
- 事件触发
- 发布/订阅者模式
- promise
- generate
方法介绍
回调函数
回调函数应该属于最简单粗暴的一种方式,主要表现为在异步函数中将一个函数进行参数传入,当异步执行完成之后执行该函数

这种写法最大的问题是:如果存在这样的一个业务场景,有三个异步函数A,B,C,其中B的执行需要在A执行结束之后,C的执行需要在B之后,这样的场景模拟成代码就是(jquery中ajax方法为例)

试想,如果再多几个异步函数,代码整体的维护性,可读性都变的极差,如果出了bug,修复的排查过程也变的极为困难,这个便是所谓的 回调函数地狱。
事件监听
事件监听最常用的常见在于DOM元素事件绑定触发,如果我们想在DOM元素与用户进行鼠标或其他交互之后执行某些逻辑,就可以使用事件监听了

引申开来,我们可以创建一个Event类,定义on和emit方法来绑定和触发自定义事件

同样回到在回调函数上遇到的难题,遇到多层嵌套的异步问题,使用事件触发的方法如何进行解决?

嗯,从写法上来看,相比于回调函数,整洁了一些。
发布/订阅者模式
订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某发布者对象。这个发布者对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。vue就是基于发布/订阅者模式。
如何通过代码实现一个发布/订阅者模式?
引用了《Pro JavaScript Design Patterns》第15章的例子
//创建一个主题发布类
var Publisher=function(){
this.subscribers=[]
}
Publisher.prototype.publish=function(data){
this.subscribers.forEach(function(fn){
fn(data)
})
}
/*
在Function上挂载这个些方法,所有的函数都可以调用这些方法
表示所有函数都可以订阅/取消订阅相关的主题发布
*/
//订阅
Function.prototype.subscribe=function(publisher){
var that=this;
var isExist=publisher.subscribers.some(function(el){
if(el===that){
return true
}
})
if(!isExist){
publisher.subscribers.push(that)
}
//return this是为了支持链式调用
return this
}
//取消订阅
Function.prototype.unsubscribe=function(publisher){
var that=this;
//就是将函数从发布者的订阅者列表中进行删除
publisher.subscribers=publisher.subscribers.filter(function(el){
if(el!==that){
return true
}
})
return this
}
var publisher=new Publisher();
var subscriberObj=function(data){
console.log(data)
}
subscriberObj.subscribe(publisher)
这样就实现了一个简单的发布订阅者模式,每次发布者发布新内容时,就会调用publish方法,然后将内容作为参数,依次调用订阅者函数(subscribers)。
其实,发布/订阅模式与事件监听很类似,
- 事件监听是将一个回调函数和事件绑定在一起,触发了相应事件,就会执行相应的回调函数
- 发布/订阅模式是将订阅函数放入了发布者的订阅者列表中,更新时,遍历订阅者列表,执行所有的订阅者函数
promise
promise由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

简单来讲,就还是promise中有着三种状态pending,fulfilled,rejected。在代码中我们可以控制状态的变更

创建一个Promise对象需要传入一个函数,函数的参数是resolve和reject,在函数内部调用时,就分别代表状态由pending=>fulfilled(成功),pending=>rejected(失败)
一旦promise状态发生变化之后,之后状态就不会再变了。比如:调用resolve之后,状态就变为fulfilled,之后再调用reject,状态也不会变化
Promise对象在创建之后会立刻执行,因此一般的做法是使用一个函数进行包装,然后return一个promise对象
function timeout(){
return new Promise(function(resolve,reject){
...//异步操作
})
}
在使用时可以通过promise对象的内置方法then进行调用,then有两个函数参数,分别表示promise对象中调用resolve和reject时执行的函数
function timeout(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve();
},1000)
})
}
timeout()
.then(function(){
...//对应resolve时执行的逻辑
},function(){
...//对应reject时执行的逻辑
})
可以使用多个then来实现链式调用,then的函数参数中会默认返回promise对象
timeout()
.then(function(){
...//对应resolve时执行的逻辑
},function(){
...//对应reject时执行的逻辑
})
.then(function(){
...//上一个then返回的promise对象对应resolve状态时执行的逻辑
},function(){
...//上一个then返回的promise对象对应reject状态时执行的逻辑
})
使用promise来解决回调地狱的做法就是使用then的链式调用
function fnA(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
function fnB(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
function fnC(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
fnA()
.then(()=>{
return fnB()
})
.then(()=>{
return fnC()
})
清晰直观了许多
generate函数
创建一个generate函数很简单
function* gen(){
yield 1
yield 2
return 3
}
区别于普通函数的地方在于function后面的*号,以及函数内部的yield。
*号是定义方式,带有 * 号表示是一个generate函数,yield是其内部独特的语法。
function* gen(){
yield 1
yield 2
return 3
}
let g=gen();
console.log(g.next())//{value:1,done:false}
console.log(g.next())//{value:2,done:false}
console.log(g.next())//{value:3,done:true}
console.log(g.next())//{value:undefined,done:true}
调用generate函数会生成一个遍历器对象,不会立即执行,需要调用next执行,执行到带有yield的那一步,next会返回一个对象,对象中value表示yield或return后的值,done表示函数是否已经执行结束(是否已经执行到return)。之后每次执行next都会从上一个yield开始继续执行
function* gen(){
let res=yield 1
yield res
return 3
}
let g=gen();
console.log(g.next())//{value:1,done:false}
console.log(g.next(333))//{value:333,done:false}
在next中传入参数会作为上一次yield的返回值(会忽略第一个next中传递的参数)
但是如何使用generate函数来进行异步编程?
这里可以使用ES2017中的async和await语法(其实属于generate函数的语法糖)
使用async替换*号,使用await替换yield就将generate函数改造成了一个async函数
async function test(){
await 1
await asyncFn()
}
test()
其中await后面可以跟promise和原始数据类型(相当与同步操作),而async返回一个promise对象(因此可以使用then进行链式调用),使用时直接调用就行了
我们再来看看如何解决回调地狱的问题
function fnA(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
function fnB(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
function fnC(){
return new Promise(resolve=>{
...//异步操作中resolve
})
}
async function gen(){
let resA=await fnA()
let resB=await fnB(resA)
let resC=await fnC(resB)
}
gen()
相比于之前的方法更简单直观,也更容易理解整体的代码流程。
作者:醉里挑灯看剑_0696
链接:https://www.jianshu.com/p/80dcd01d415e
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
关于js中异步问题的解决方案的更多相关文章
- Web worker 与JS中异步编程的对比
0.从一道题说起 var t = true; setTimeout(function(){ t = false; }, 1000); while(t){ } alert('end'); 问,以上代码何 ...
- js中精度问题以及解决方案
js中的数字按照IEEE 754的标准,使用64位双精度浮点型来表示.其中符号位S,指数位E,尾数位M分别占了1,11,52位,并且在ES5规范中指出了指数位E的取值范围是[-1074, 971]. ...
- js中异步方案比较完整版(callback,promise,generator,async)
JS 异步已经告一段落了,这里来一波小总结 1. 回调函数(callback) setTimeout(() => { // callback 函数体 }, 1000) 缺点:回调地狱,不能用 t ...
- 【译】深入理解python3.4中Asyncio库与Node.js的异步IO机制
转载自http://xidui.github.io/2015/10/29/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3python3-4-Asyncio%E5%BA%93% ...
- promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解
* promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ...
- js中的同步与异步的问题
前言 近来,总是忙于拿js写一些案例,因为是小白,并没有什么丰富的经验,对各个知识点把握也不是很全面,写起来真的是...一言难尽,太痛苦了= =.尤其是在写一些轮播的时候,里面需要用到定时器,而一旦用 ...
- JS中遍历EL表达式中后台传过来的Java集合
前言:在我的项目里有这么一个情况,后台直接model.addAttribute()存储了一个对象,此对象内部有一个集合,前端JSP处理的方法正常情况下就是直接使用EL表达式即可.但是如果在JS中需要使 ...
- 小程序首页onLoad为异步,调用app.js中的全局参数的解决方案。
一,先说一下遇到的问题: 在首页,为了携带app.js中一些参数去做请求动作,但是由于异步原因,发现请求时候,参数信息还未获取到但请求已经发出去. 若等app.js的全局参数返回来,再携带着它去做请求 ...
- js中的异步与同步,解决由异步引起的问题
之前在项目中遇到过好多次因为异步引起的变量没有值,所以意识到了认识js中同步与异步机制的重要性 在单线程的js中,异步代码会被放入一个事件队列,等到所有其他代码执行后再执行,而不会阻塞线程. 下面是j ...
随机推荐
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 排版:标题
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 显示代码:电脑程序输出: Sample output
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- springboot下使用dubbo的简单demo
1.一些话 现在java后端开发大多用springboot来简化环境搭建,现在一直使用的是springcloud和k8s有关的东西,以前用过dubbo,但那会儿的开发环境搭建流程较为繁琐,而且不支持r ...
- Web - 实用组件
1, vue-awesome-swiper 基于 Swiper4.适用于 Vue 的轮播组件,支持服务端渲染和单页应用. 2, http://www.spritecow.com/ 雪碧图背 ...
- ActiveMQ--模式(队列模式/主题模式)
两种模式:队列模式/主题模式 pom.xml <dependency> <groupId>org.apache.activemq</groupId> <art ...
- 多个span标签在同一行显示
属性设置为display:inline或display:inline-block
- Percona XtraBackup不锁库搭建slave数据库-基于GTID
Percona XtraBackup不锁库搭建slave数据库-基于GTID 1.下载安装epel源并安装 wget http://ftp.cuhk.edu.hk/pub/linux/fedora-e ...
- 嵌入式 printf的实现
在嵌入式中,经常需要用到printf来调试程序 标准库函数的默认输出设备是显示器,要实现在串口或LCD输出,必须重定义标准库函数里调用的与输出设备相关的函数. printf输出到串口,需要将fputc ...
- 《跟老齐学Python:从入门到精通》齐伟(编著)epub+mobi+azw3
内容简介 <跟老齐学Python:从入门到精通>是面向编程零基础读者的Python入门教程,内容涵盖了Python的基础知识和初步应用.以比较轻快的风格,向零基础的学习者介绍一门时下比较流 ...
- LIS问题
LIS定义LIS(Longest Increasing Subsequence)最长上升子序列 .一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的. ...