再谈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 相关文章推荐
jquery中ajax调用json数据的使用说明
Mar 17 Javascript
JavaScript 中的日期和时间及表示标准介绍
Aug 21 Javascript
js实现幻灯片效果(基于jquery插件)
Nov 05 Javascript
js日期对象兼容性的处理方法
Jan 28 Javascript
javascript实现的平方米、亩、公顷单位换算小程序
Aug 11 Javascript
实用框架(iframe)操作代码
Oct 23 Javascript
jQuery动态添加及删除表单上传元素的方法(附demo源码下载)
Jan 15 Javascript
整理一些最近经常遇到的前端面试题
Apr 25 Javascript
VueJS如何引入css或者less文件的一些坑
Apr 25 Javascript
快速理解 JavaScript 中的 LHS 和 RHS 查询的用法
Aug 24 Javascript
React事件处理的机制及原理
Dec 03 Javascript
JQuery复选框全选效果如何实现
May 08 jQuery
简单介绍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
第五节 克隆 [5]
2006/10/09 PHP
PHP简单系统查询模块代码打包下载
2008/06/07 PHP
PHP 压缩文件夹的类代码
2009/11/05 PHP
php实现微信扫码支付
2017/03/26 PHP
PHP实现Redis单据锁以及防止并发重复写入
2018/04/10 PHP
js 发个判断字符串是否为符合标准的函数
2009/04/27 Javascript
原始的js代码和jquery对比体会
2013/09/10 Javascript
按Enter键触发事件的jquery方法实现代码
2014/02/17 Javascript
微信小程序 scroll-view隐藏滚动条详解
2017/01/16 Javascript
利用JavaScript的%做隔行换色的实例
2017/11/25 Javascript
了解JavaScript中let语句
2019/05/30 Javascript
python实现巡检系统(solaris)示例
2014/04/02 Python
python分割文件的常用方法
2014/11/01 Python
python matplotlib中文显示参数设置解析
2017/12/15 Python
20个常用Python运维库和模块
2018/02/12 Python
python TKinter获取文本框内容的方法
2018/10/11 Python
Numpy将二维数组添加到空数组的实现
2019/12/05 Python
Python 实现顺序高斯消元法示例
2019/12/09 Python
python实现udp聊天窗口
2020/03/31 Python
使用Python实现将多表分批次从数据库导出到Excel
2020/05/15 Python
Python如何优雅删除字符列表空字符及None元素
2020/06/25 Python
python获取本周、上周、本月、上月及本季的时间代码实例
2020/09/08 Python
Python gevent协程切换实现详解
2020/09/14 Python
CSS3+font字体文件实现圆形半透明菜单具体步骤(图解)
2013/06/03 HTML / CSS
关于HTML5的安全问题开发人员需要牢记的
2012/06/21 HTML / CSS
艺术系应届生的自我评价
2013/10/19 职场文书
教学个人的自我评价分享
2014/02/16 职场文书
物业总经理岗位职责
2014/02/28 职场文书
中式婚礼主持词
2014/03/13 职场文书
财务经理岗位职责
2015/01/31 职场文书
2015高考寄语集锦
2015/02/27 职场文书
2015公务员年度考核评语
2015/03/25 职场文书
音乐剧猫观后感
2015/06/04 职场文书
爱国主义教育基地观后感
2015/06/18 职场文书
入党申请书怎么写?
2019/06/21 职场文书
浅谈JS和Nodejs中的事件驱动
2021/05/05 NodeJs