新手如何快速理解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 相关文章推荐
jQuery学习3:操作元素属性和特性
Feb 07 Javascript
JavaScript自定义方法实现trim()、Ltrim()、Rtrim()的功能
Nov 03 Javascript
jquery分页插件jpaginate在IE中不兼容问题
Apr 22 Javascript
node.js中的fs.readlinkSync方法使用说明
Dec 17 Javascript
jQuery使用$.ajax进行异步刷新的方法(附demo下载)
Dec 04 Javascript
jQuery 获取跨域XML(RSS)数据的相关总结分析
May 18 Javascript
深入浅析JavaScript的API设计原则
Jun 14 Javascript
js Date()日期函数浏览器兼容问题解决方法
Sep 12 Javascript
微信小程序车牌号码模拟键盘输入功能的实现代码
Nov 11 Javascript
详解iframe跨域的几种常用方法(小结)
Apr 29 Javascript
Node.js 获取微信JS-SDK CONFIG的方法示例
May 21 Javascript
React如何使用axios请求数据并把数据渲染到组件
Aug 05 Javascript
简单了解小程序+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
实现“上一页”和“下一页按钮
2006/10/09 PHP
php类的定义与继承用法实例
2015/07/07 PHP
如何用js控制css中的float的代码
2007/08/16 Javascript
javascript中interval与setTimeOut的区别示例介绍
2014/03/14 Javascript
用js代码和插件实现wordpress雪花飘落效果的四种方法
2014/12/15 Javascript
jQuery中 prop() attr()使用详解
2015/05/19 Javascript
jquery实现两个图片渐变切换效果的方法
2015/06/25 Javascript
javascript设置和获取cookie的方法实例详解
2016/01/05 Javascript
前端设计师们最常用的JS代码汇总
2016/09/25 Javascript
Vue 过渡实现轮播图效果
2017/03/27 Javascript
微信小程序学习之数据处理详解
2017/07/05 Javascript
解决微信浏览器缓存站点入口文件(IIS部署Vue项目)
2019/06/17 Javascript
微信小程序wx.request拦截器使用详解
2019/07/09 Javascript
使用Python脚本将Bing的每日图片作为桌面的教程
2015/05/04 Python
Python+selenium实现截图图片并保存截取的图片
2018/01/05 Python
Python基于辗转相除法求解最大公约数的方法示例
2018/04/04 Python
Python实现账号密码输错三次即锁定功能简单示例
2019/03/29 Python
Python中面向对象你应该知道的一下知识
2019/07/10 Python
Python综合应用名片管理系统案例详解
2020/01/03 Python
使用python执行shell脚本 并动态传参 及subprocess的使用详解
2020/03/06 Python
python 获取当前目录下的文件目录和文件名实例代码详解
2020/03/10 Python
Pycharm配置PyQt5环境的教程
2020/04/02 Python
如何在Win10系统使用Python3连接Hive
2020/10/15 Python
英国音乐设备和乐器商店:Gear4music
2017/10/16 全球购物
发现世界上最好的珠宝设计师:JewelStreet
2017/12/17 全球购物
如何估计一张表的大小(假设该表中有1万条数据)
2016/03/27 面试题
SQL注入攻击的种类有哪些
2013/12/30 面试题
教师实习自我鉴定
2013/12/11 职场文书
小学生美德少年事迹
2014/02/02 职场文书
社区健康教育实施方案
2014/03/18 职场文书
2015建军节87周年演讲稿
2015/03/19 职场文书
员工手册编写范本
2015/05/14 职场文书
小兵张嘎观后感300字
2015/06/03 职场文书
原来闭幕词是这样写的呀!
2019/07/01 职场文书
七年级作文(600字3篇)
2019/09/24 职场文书
90条交通安全宣传标语
2019/10/12 职场文书