再谈JavaScript异步编程


Posted in Javascript onJanuary 27, 2016

随着前端的发展,异步这个词真是越来越常见了。假设我们现在有这么一个异步任务:

向服务器发起数次请求,每次请求的结果作为下次请求的参数。
来看看我们都有哪些处理方法:

Callbacks

最先想到也是最常用的便是回调函数了,我们来进行简单的封装:

let makeAjaxCall = (url, cb) => {
  // do some ajax
  // callback with result
}

makeAjaxCall('http://url1', (result) => {
  result = JSON.parse(result)
})

嗯,看起来还不错!但是当我们尝试嵌套多个任务时,代码看起来会是这样的:

makeAjaxCall('http://url1', (result) => {
  result = JSON.parse(result)

  makeAjaxCall(`http://url2?q=${result.query}`, (result) => {
    result = JSON.parse(result)

    makeAjaxCall(`http://url3?q=${result.query}`, (result) => {
      // ...
    })
  })
})

天哪!快让那堆 }) 见鬼去吧!

于是,我们想尝试借助 JavaScript 事件模型

1、Pub/Sub

在 DOM 事件的处理中,Pub/Sub 是一种很常见的机制,比如我们要为元素加上事件监听:

elem.addEventListener(type, (evt) => {
  // handler
})

所以我们是不是也可以构造一个类似的模型来处理异步任务呢?

首先是要构建一个分发中心,并添加 on / emit 方法:

let PubSub = {
  events: {},
  on(type, handler) {
    let events = this.events
    events[type] = events[type] || []
    events[type].push(handler)
  },
  emit(type, ...datas) {
    let events = this.events

    if (!events[type]) {
      return
    }

    events[type].forEach((handler) => handler(...datas))
  }
}

然后我们便可以这样使用:

const urls = [
  'http://url1',
  'http://url2',
  'http://url3'
]

let makeAjaxCall = (url) => {
  // do some ajax
  PubSub.emit('ajaxEnd', result)
}

let subscribe = (urls) => {
  let index = 0

  PubSub.on('ajaxEnd', (result) => {
    result = JSON.parse(result)

    if (urls[++index]) {
      makeAjaxCall(`${urls[index]}?q=${result.query}`)
    }
  })

  makeAjaxCall(urls[0])
}

比起回调函数好像没有什么革命性的改变,但是这样做的好处是:我们可以将请求和处理函数放在不同的模块中,减少耦合。

2、Promise

真正带来革命性改变的是 Promise 规范。借助 Promise,我们可以这样完成异步任务:

let makeAjaxCall = (url) => {
  return new Promise((resolve, reject) => {
    // do some ajax
    resolve(result)
  })
}

makeAjaxCall('http://url1')
  .then(JSON.parse)
  .then((result) => makeAjaxCall(`http://url2?q=${result.query}`))
  .then(JSON.parse)
  .then((result) => makeAjaxCall(`http://url3?q=${result.query}`))

好棒!写起来像同步处理的函数一样!

别着急,少年。我们还有更棒的:

3、Generators

ES6 的另外一个大杀器便是 Generators[2]。在一个 generator function 中,我们可以通过 yield 语句来中断函数的执行,并在函数外部通过 next 方法来迭代语句,更重要的是我们可以通过 next 方法向函数内部注入数据,动态改变函数的行为。比如:

function* gen() {
  let a = yield 1
  let b = yield a * 2
  return b
}

let it = gen()

it.next() // output: {value: 1, done: false}
it.next(10) // a = 10, output: {value: 20, done: false}
it.next(100) // b = 100, output: {value: 100, done: true}

通过 generator 将我们之前的 makeAjaxCall 函数进行封装:

let makeAjaxCall = (url) => {
  // do some ajax
  iterator.next(result)
}

function* requests() {
  let result = yield makeAjaxCall('http://url1')
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url2?q=${result.query}`)
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url3?q=${result.query}`)
}

let iterator = requests()
iterator.next() // get everything start

哦!看起来逻辑很清楚的样子,但是每次都得从外部注入 iterator 感觉好不舒服……

别急,我们让 Promise 和 Generator 混合一下,看会产出什么黑魔法:

let makeAjaxCall = (url) => {
  return new Promise((resolve, reject) => {
    // do some ajax
    resolve(result)
  })
}

let runGen = (gen) => { 
  let it = gen()

  let continuer = (value, err) => {
    let ret

    try {
      ret = err ? it.throw(err) : it.next(value)
    } catch (e) {
      return Promise.reject(e)
    }

    if (ret.done) {
      return ret.value
    }

    return Promise
      .resolve(ret.value)
      .then(continuer)
      .catch((e) => continuer(null, e))
  }

  return continuer()
}

function* requests() {
  let result = yield makeAjaxCall('http://url1')
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url2?q=${result.query}`)
  result = JSON.parse(result)
  result = yield makeAjaxCall(`http://url3?q=${result.query}`)
}

runGen(requests)

runGen 函数看起来像个自动机一样,好厉害!

实际上,这个 runGen 的方法是对 ECMAScript 7 async function 的一个实现:

4、async function

ES7 中,引入了一个更自然的特性 async function[3]。利用 async function 我们可以这样完成任务:

let makeAjaxCall = (url) => {
  return new Promise((resolve, reject) => {
    // do some ajax
    resolve(result)
  })
}

;(async () => {
  let result = await makeAjaxCall('http://url1')
  result = JSON.parse(result)
  result = await makeAjaxCall(`http://url2?q=${result.query}`)
  result = JSON.parse(result)
  result = await makeAjaxCall(`http://url3?q=${result.query}`)
})()

就像我们在上文把 Promise 和 Generator 结合在一起时一样,await 关键字后同样接受一个 Promise。在 async function 中,只有在 await 后的语句完成后剩下的语句才会被执行,整个过程就像我们用 runGen 函数封装 Generator 一样。

以上就是本文总结的几种JavaScript 异步编程模式,希望对大家的学习有所帮助。

Javascript 相关文章推荐
分享27个jQuery 表单插件集合推荐
Apr 25 Javascript
JQuery验证工具类搜集整理
Jan 16 Javascript
from 表单提交返回值用post或者是get方法实现
Aug 21 Javascript
JavaScript检测弹出窗口是否已经关闭的方法
Mar 24 Javascript
Javascript BOM学习小结(六)
Nov 26 Javascript
jQuery+JSON实现AJAX二级联动实例分析
Dec 18 Javascript
BootStrap入门教程(三)之响应式原理
Sep 19 Javascript
浅谈Vue 初始化性能优化
Aug 31 Javascript
JS+Canvas绘制动态时钟效果
Nov 10 Javascript
基于Vue的移动端图片裁剪组件功能
Nov 28 Javascript
原生js检测页面加载完毕的实例
Sep 11 Javascript
小程序实现简单语音聊天的示例代码
Jul 24 Javascript
简单介绍jsonp 使用小结
Jan 27 #Javascript
理解javascript异步编程
Jan 27 #Javascript
js实现的鼠标滚轮滚动切换页面效果(类似360默认页面滚动切换效果)
Jan 27 #Javascript
AngularJS转换响应内容
Jan 27 #Javascript
jQuery+css实现的切换图片功能代码
Jan 27 #Javascript
javascript中的3种继承实现方法
Jan 27 #Javascript
jQuery+css实现的换页标签栏效果
Jan 27 #Javascript
You might like
PHP 强制下载文件代码
2010/10/24 PHP
destoon实现调用热门关键字的方法
2014/07/15 PHP
php提取身份证号码中的生日日期以及验证是否为成年人的函数
2015/09/29 PHP
使用JQuery进行跨域请求
2010/01/25 Javascript
JavaScript代码简单实现求杨辉三角给定行的最大值
2013/10/29 Javascript
表格奇偶行设置不同颜色的核心JS代码
2013/12/24 Javascript
javascript的创建多行字符串的7种方法
2014/04/29 Javascript
详解jQuery中的DOM操作
2016/12/23 Javascript
jQuery插件HighCharts绘制简单2D折线图效果示例【附demo源码】
2017/03/21 jQuery
vue动态改变背景图片demo分享
2018/09/13 Javascript
快速搭建Node.js(Express)用户注册、登录以及授权的方法
2019/05/09 Javascript
基于js实现抽红包并分配代码实例
2019/09/19 Javascript
[03:54]DOTA2英雄梦之声_第06期_昆卡
2014/06/23 DOTA
[05:15]DOTA2英雄梦之声_第16期_灰烬之灵
2014/06/21 DOTA
[00:52]黑暗之门更新 新英雄孽主驾临DOTA2
2016/08/24 DOTA
python类继承与子类实例初始化用法分析
2015/04/17 Python
python生成词云的实现方法(推荐)
2017/06/13 Python
Python进阶之自定义对象实现切片功能
2019/01/07 Python
Python 中的 import 机制之实现远程导入模块
2019/10/29 Python
python使用paramiko实现ssh的功能详解
2020/03/06 Python
Python中的xlrd模块使用原理解析
2020/05/21 Python
使用Python绘制台风轨迹图的示例代码
2020/09/21 Python
html5 touch事件实现页面上下滑动效果【附代码】
2016/03/10 HTML / CSS
香港永安旅游网:Wing On Travel
2017/04/10 全球购物
英国天然有机美容护肤品:Neal’s Yard Remedies
2018/05/05 全球购物
德国滑雪和户外用品网上商店:XSPO
2019/10/30 全球购物
打造完美自荐信
2014/01/24 职场文书
公司联欢会策划方案
2014/05/19 职场文书
女生节标语
2014/06/26 职场文书
我与祖国共奋进演讲稿
2014/09/13 职场文书
授权委托书样本及填写说明
2014/09/19 职场文书
2015大学生自我评价范文
2015/03/03 职场文书
nginx实现发布静态资源的方法
2021/03/31 Servers
MySQL 开窗函数
2022/02/15 MySQL
使用Python开发贪吃蛇游戏 SnakeGame
2022/04/30 Python
利用Redis实现点赞功能的示例代码
2022/06/28 Redis