新手如何快速理解js异步编程


Posted in Javascript onJune 24, 2019

前言

异步编程从早期的 callback、事件发布\订阅模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维。

以这条暗线将上述几种解决方案连在一起,就可以更好地理解异步编程的原理、魅力。
├── 事件发布\订阅模式 <= Callback
├── Promise <= 事件发布\订阅模式
├── Async、Await <= Promise、Generator

事件发布\订阅模式 <= Callback

这个模式本质上就是回调函数的事件化。它本身并无同步、异步调用的问题,我们只是使用它来实现事件与回调函数之间的关联。比较典型的有 NodeJS 的 events 模块

const { EventEmitter } = require('events')
const eventEmitter = new EventEmitter()
// 订阅
eventEmitter.on("event", function(msg) {
console.log("event", msg)
})
// 发布
eventEmitter.emit("event", "Hello world")

那么这种模式是如何与 Callback 关联的呢?我们可以利用 Javascript 简单实现 EventEmitter,答案就显而易见了。

class usrEventEmitter {
constructor () {
this.listeners = {}
}
// 订阅,callback 为每个 event 的侦听器
on(eventName, callback) {
if (!this.listeners[eventName]) this.listeners[eventName] = []
this.listeners[eventName].push(callback)
}
// 发布
emit(eventName, params) {
this.listeners[eventName].forEach(callback => {
callback(params)
})
}
// 注销
off(eventName, callback) {
const rest = this.listeners[eventName].fitler(elem => elem !== callback)
this.listeners[eventName] = rest
}
// 订阅一次
once(eventName, callback) { 
const handler = function() {
callback()
this.off(eventName, handler)
}
this.on(eventName, handler)
}
}

上述实现忽略了很多细节,例如异常处理、多参数传递等。只是为了展示事件订阅\发布模式。

很明显的看出,我们使用这种设计模式对异步编程做了逻辑上的分离,将其语义化为

// 一些事件可能会被触发
eventEmitter.on
// 当它发生的时候,要这样处理
eventEmitter.emit

也就是说,我们将最初的 Callback 变成了事件监听器,从而优雅地解决异步编程。

Promise <= 事件发布\订阅模式

使用事件发布\订阅模式时,需要我们事先严谨地设置目标,也就是上面所说的,必须要缜密地设定好有哪些事件会发生。这与我们语言的线性思维很违和。那么有没有一种方式可以解决这个问题,社区产出了 Promise。

const promise = new Promise(function(resolve, reject) {
try {
setTimeout(() => {
resolve('hello world')
}, 500)
} catch (error) {
reject(error)
}
})
// 语义就变为先发生一些异步行为,then 我们应该这么处理
promise.then(msg => console.log(msg)).catch(error => console.log('err', error))

那么这种 Promise 与事件发布\订阅模式有什么联系呢?我们可以利用 EventEmitter 来实现 Promise,这样可能会对你有所启发。

我们可以将 Promise 视为一个 EventEmitter,它包含了 { state: 'pending' } 来描述当前的状态,同时侦听它的变化

  • 当成功时 { state: 'fulfilled' },要做些什么 on('resolve', callback);
  • 当失败时 { state: 'rejected' },要做些什么 on('reject', callback)。

具体实现如下

const { EventEmitter } = require('events')
class usrPromise extends EventEmitter {
// 构造时候执行
constructor(executor) {
super()
// 发布
const resolve = (value) => this.emit('resolve', value)
const reject = (reason) => this.emit('reject', reason)
if (executor) {
// 模拟 event loop,注此处利用 Macrotask 来模拟 Microtask
setTimeout(() => executor(resolve, reject))
}
}
then(resolveHandler, rejectHandler) {
const nextPromise = new usrPromise()
// 订阅 resolve 事件
if (resolveHandler) {
const resolve = (data) => {
const result = resolveHandler(data)
nextPromise.emit('resolve', result)
}
this.on('resolve', resolve)
}
// 订阅 reject 事件
if (rejectHandler) {
const reject = (data) => {
const result = rejectHandler(data)
nextPromise.emit('reject', result)
}
this.on('reject', reject)
} else {
this.on('reject', (data) => {
promise.emit('reject', data)
})
}
return nextPromise
}
catch(handler) {
this.on('reject', handler)
}
}

我们使用 then 方法来将预先需要定义的事件侦听器存放起来,同时在 executor 中设定这些事件该在什么时候实行。

可以看出从事件发布\订阅模式到 Promise,带来了语义上的巨大变革,但是还是需要使用 new Promise 来描述整个状态的转换,那么有没有更好地实现方式呢?

async、await <= Promise、Generator

async、await 标准是 ES 2017 引入,提供一种更加简洁的异步解决方案。

async function say(greeting) {
return new Promise(function(resolve, then) {
setTimeout(function() {
resolve(greeting)
}, 1500)
})
}
;(async function() {
let v1 = await say('Hello')
console.log(v1)
let v2 = await say('World')
console.log(v2)
})()

await 可以理解为暂停当前 async function 的执行,等待 Promise 处理完成。。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值。

async、await 的出现,减少了多个 then 的链式调用形式的代码。下面我们结合 Promise 与 Generator 来实现 async、await

function async(makeGenerator) {
return function() {
const generator = makeGenerator.apply(this, arguments)
function handle({ value, done }) {
if (done === true) return Promise.resolve(value)
return Promise.resolve(value).then(
(res) => {
return handle(generator.next(res))
},
function(err) {
return handle(generator.throw(err))
}
)
}
try {
return handle(generator.next())
} catch (ex) {
return Promise.reject(ex)
}
}
}
async(function*() {
var v1 = yield say('hello')
console.log(1, v1)
var v2 = yield say('world')
console.log(2, v2)
})()

本质上就是利用递归完成 function* () { ... } 的自动执行。相比与 Generator 函数,这种形式无需手动执行,并且具有更好的语义。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,

Javascript 相关文章推荐
JavaScript 拾漏补遗
Dec 27 Javascript
window.onbeforeunload方法在IE下无法正常工作的解决办法
Jan 23 Javascript
javascript offsetX与layerX区别
Mar 12 Javascript
javascript实现微信分享
Dec 23 Javascript
jQuery判断是否存在滚动条的简单方法
Sep 17 Javascript
vue 使用Jade模板写html,stylus写css的方法
Feb 23 Javascript
vue2.0 自定义 饼状图 (Echarts)组件的方法
Mar 02 Javascript
基于vue-video-player自定义播放器的方法
Mar 21 Javascript
vue中利用Promise封装jsonp并调取数据
Jun 18 Javascript
在Vue中使用this.$store或者是$route一直报错的解决
Nov 08 Javascript
详解vue beforeEach 死循环问题解决方法
Feb 25 Javascript
vue使用transition组件动画效果的实例代码
Jan 28 Vue.js
简单了解小程序+node梳理登陆流程
Jun 24 #Javascript
JS数组扁平化(flat)方法总结详解
Jun 24 #Javascript
深入了解query和params的使用区别
Jun 24 #Javascript
如何使用JavaScript实现栈与队列
Jun 24 #Javascript
简单了解JavaScript中的执行上下文和堆栈
Jun 24 #Javascript
一次让你了解全部JavaScript的作用域
Jun 24 #Javascript
通过循环优化 JavaScript 程序
Jun 24 #Javascript
You might like
用PHP调用Oracle存储过程的方法
2008/09/12 PHP
PHP过滤★等特殊符号的正则
2014/01/27 PHP
ThinkPHP令牌验证实例
2014/06/18 PHP
ThinkPHP CURD方法之table方法详解
2014/06/18 PHP
thinkphp如何获取客户端IP
2015/11/03 PHP
Laravel学习教程之本地化模块
2017/08/18 PHP
在JavaScript里嵌入大量字符串常量的实现方法
2013/07/07 Javascript
javascript:FF/Chrome与IE动态加载元素的区别说明
2014/01/26 Javascript
JavaScript编程的10个实用小技巧
2014/04/18 Javascript
关于js二维数组和多维数组的定义声明(详解)
2016/10/02 Javascript
基于vue实现分页/翻页组件paginator示例
2017/03/09 Javascript
使用JQuery实现图片轮播效果的实例(推荐)
2017/10/24 jQuery
vue实现压缩图片预览并上传功能(promise封装)
2019/01/10 Javascript
JavaScript实现省份城市的三级联动
2020/02/11 Javascript
浅谈element中InfiniteScroll按需引入的一点注意事项
2020/06/05 Javascript
[09:43]DOTA2每周TOP10 精彩击杀集锦vol.5
2014/06/25 DOTA
[01:32]DOTA2上海特锦赛现场采访:最想COS的英雄
2016/03/25 DOTA
[05:15]2018年度CS GO社区贡献奖-完美盛典
2018/12/16 DOTA
Python中使用PyHook监听鼠标和键盘事件实例
2014/07/18 Python
Python随机数random模块使用指南
2016/09/09 Python
请不要重复犯我在学习Python和Linux系统上的错误
2016/12/12 Python
python爬虫 基于requests模块发起ajax的get请求实现解析
2019/08/20 Python
pytorch nn.Conv2d()中的padding以及输出大小方式
2020/01/10 Python
pycharm实现print输出保存到txt文件
2020/06/01 Python
Python实现一个优先级队列的方法
2020/07/31 Python
美国老牌主机服务商:iPage
2016/07/22 全球购物
DHC美国官网:日本通信销售第一的化妆品品牌
2017/11/12 全球购物
main 主函数执行完毕后,是否可能会再执行一段代码,给出说明
2012/12/05 面试题
银行贷款承诺书
2014/03/29 职场文书
募捐倡议书怎么写
2014/05/14 职场文书
指导教师推荐意见
2015/06/05 职场文书
机械原理课程设计心得体会
2016/01/15 职场文书
《弟子规》读后感:知廉耻、明是非、懂荣辱、辨善恶
2019/12/03 职场文书
如何利用Matlab制作一款真正的拼图小游戏
2021/05/11 Python
pytorch中[..., 0]的用法说明
2021/05/20 Python
关于Python OS模块常用文件/目录函数详解
2021/07/01 Python