再谈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 相关文章推荐
MooTools 1.2中的Drag.Move来实现拖放
Sep 15 Javascript
JQuery实现的在新窗口打开链接的方法小结
Apr 22 Javascript
基于Asp.net与Javascript控制的日期控件
May 22 Javascript
jQuery.Highcharts.js绘制柱状图饼状图曲线图
Mar 14 Javascript
Jquery使用小技巧汇总
Dec 29 Javascript
JS实现鼠标滑过显示边框的菜单效果
Sep 21 Javascript
微信小程序 navigation API实例详解
Oct 02 Javascript
JS+HTML5实现的前端购物车功能插件实例【附demo源码下载】
Oct 17 Javascript
js上传图片预览的实现方法
May 09 Javascript
javascript实现的字符串转换成数组操作示例
Jun 13 Javascript
JS根据Unix时间戳显示发布时间是多久前【项目实测】
Jul 10 Javascript
Java无向树分析 实现最小高度树
Apr 09 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
同台服务器使用缓存APC效率高于Memcached的演示代码
2010/02/16 PHP
比较时间段一与时间段二是否有交集的php函数
2011/05/31 PHP
PHP类的特性实例分析
2016/09/28 PHP
Laravel5.* 打印出执行的sql语句的方法
2017/07/24 PHP
关于IE、Firefox、Opera页面呈现异同 写脚本很痛苦
2009/08/28 Javascript
JavaScript之编码规范 推荐
2012/05/23 Javascript
jquery与js函数冲突的两种解决方法
2013/09/09 Javascript
node.js中的buffer.Buffer.byteLength方法使用说明
2014/12/10 Javascript
了不起的node.js读书笔记之node的学习总结
2014/12/22 Javascript
JS 作用域与作用域链详解
2015/04/07 Javascript
jQuery中attr()与prop()函数用法实例详解(附用法区别)
2015/12/29 Javascript
详解Javascript中的Object对象
2016/02/28 Javascript
Bootstrap Search Suggest使用例子
2016/12/21 Javascript
Bootstrap源码学习笔记之bootstrap进度条
2016/12/24 Javascript
JavaScript单线程和任务队列原理解析
2020/02/04 Javascript
javascript实现计算器功能
2020/03/30 Javascript
vue相同路由跳转强制刷新该路由组件操作
2020/08/05 Javascript
关于IDEA中的.VUE文件报错 Export declarations are not supported by current JavaScript version
2020/10/17 Javascript
[03:01]2014DOTA2国际邀请赛 小组赛7月13日TOPPLAY
2014/07/14 DOTA
利用python求解物理学中的双弹簧质能系统详解
2017/09/29 Python
Python 错误和异常代码详解
2018/01/29 Python
Windows下anaconda安装第三方包的方法小结(tensorflow、gensim为例)
2018/04/05 Python
Python+OpenCv制作证件图片生成器的操作方法
2019/08/21 Python
Pycharm使用远程linux服务器conda/python环境在本地运行的方法(图解))
2019/12/09 Python
Python如何设置指定窗口为前台活动窗口
2020/08/12 Python
CSS3 box-shadow属性实例详解
2020/06/19 HTML / CSS
HTML5的自定义属性data-*详细介绍和JS操作实例
2014/04/10 HTML / CSS
自我介绍演讲稿
2014/01/15 职场文书
党员自我批评与反省材料
2014/02/10 职场文书
关于感恩的演讲稿800字
2014/08/26 职场文书
地震捐款倡议书
2014/08/29 职场文书
员工升职自荐信
2015/03/27 职场文书
中学教师读书笔记
2015/07/01 职场文书
教师培训简讯
2015/07/20 职场文书
检讨书格式
2019/04/25 职场文书
为什么RedisCluster设计成16384个槽
2021/09/25 Redis