再谈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 相关文章推荐
你必须知道的JavaScript 变量命名规则详解
May 07 Javascript
禁用键盘上的(全局)指定键兼容iE、Chrome、火狐
May 14 Javascript
js 自定义个性下拉选择框示例
Aug 20 Javascript
让table变成exls的示例代码
Mar 24 Javascript
用C/C++来实现 Node.js 的模块(一)
Sep 24 Javascript
Javascript Memoizer浅析
Oct 16 Javascript
jquery实现(textarea)placeholder自动换行
Dec 22 Javascript
微信小程序实现登录页云层漂浮的动画效果
May 05 Javascript
Javascript中将变量转换为字符串的三种方法
Sep 19 Javascript
Vuejs开发环境搭建及热更新【推荐】
Sep 07 Javascript
ES6的异步终极解决方案分享
Jul 11 Javascript
Javascript实现单选框效果
Dec 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
PHP命名空间namespace用法实例分析
2016/09/27 PHP
在JS数组特定索引处指定位置插入元素的技巧
2014/08/24 Javascript
分享有关jQuery中animate、slide、fade等动画的连续触发、滞后反复执行的bug
2016/01/10 Javascript
深入理解Angular4中的依赖注入
2017/06/07 Javascript
Vue计算属性的使用
2017/08/04 Javascript
Javacript中自定义的map.js  的方法
2017/11/26 Javascript
mock.js实现模拟生成假数据功能示例
2019/01/15 Javascript
js根据需要计算数组中重复出现某个元素的个数
2019/01/18 Javascript
微信公众号服务器验证Token步骤图解
2019/12/30 Javascript
vue 实现根据data中的属性值来设置不同的样式
2020/08/04 Javascript
Vue自定义表单内容检查rules实例
2020/10/30 Javascript
Python2.5/2.6实用教程 入门基础篇
2009/11/29 Python
将图片文件嵌入到wxpython代码中的实现方法
2014/08/11 Python
Python中os.path用法分析
2015/01/15 Python
python制作一个桌面便签软件
2015/08/09 Python
Python使用urllib2模块抓取HTML页面资源的实例分享
2016/05/03 Python
Python实现可设置持续运行时间、线程数及时间间隔的多线程异步post请求功能
2018/01/11 Python
Python计算一个给定时间点前一个月和后一个月第一天的方法
2018/05/29 Python
Pycharm导入Python包,模块的图文教程
2018/06/13 Python
Python常见数据类型转换操作示例
2019/05/08 Python
opencv之为图像添加边界的方法示例
2019/12/26 Python
Django models文件模型变更错误解决
2020/05/11 Python
使用OpenCV获取图像某点的颜色值,并设置某点的颜色
2020/06/02 Python
Python getsizeof()和getsize()区分详解
2020/11/20 Python
HTML5移动端开发中的Viewport标签及相关CSS用法解析
2016/04/15 HTML / CSS
戴尔美国官网:Dell
2016/08/31 全球购物
Nike意大利官网:Nike.com IT
2020/01/19 全球购物
计算机专业毕业生的自我评价
2013/11/18 职场文书
六一亲子活动总结
2014/07/01 职场文书
民主生活会对照检查材料(统计局)
2014/09/21 职场文书
志愿者事迹材料
2014/12/26 职场文书
停水通知
2015/04/16 职场文书
56句经典英文座右铭
2019/08/09 职场文书
JavaScript 去重和重复次数统计
2021/03/31 Javascript
python 办公自动化——基于pyqt5和openpyxl统计符合要求的名单
2021/05/25 Python
聊聊Lombok中的@Builder注解使用教程
2021/11/17 Java/Android