再谈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 相关文章推荐
JS版网站风格切换实例代码
Oct 06 Javascript
用Javascript来生成ftp脚本的小例子
Jul 03 Javascript
在js文件中如何获取basePath处理js路径问题
Jul 10 Javascript
node.js中的http.response.getHeader方法使用说明
Dec 14 Javascript
jQuery中prevAll()方法用法实例
Jan 08 Javascript
JavaScript实现同一页面内两个表单互相传值的方法
Aug 12 Javascript
AngularJS基础 ng-cut 指令介绍及简单示例
Aug 01 Javascript
给easyui datebox扩展一个清空的实例
Nov 09 Javascript
解析vue中的$mount
Dec 21 Javascript
JS扩展String.prototype.format字符串拼接的功能
Mar 09 Javascript
vue中使用带隐藏文本信息的图片、图片水印的方法
Apr 24 Javascript
Vue elementui字体图标显示问题解决方案
Aug 18 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
浅析HTTP消息头网页缓存控制以及header常用指令介绍
2013/06/28 PHP
详解php魔术方法(Magic methods)的使用方法
2016/02/14 PHP
实例分析10个PHP常见安全问题
2019/07/09 PHP
js onpropertychange输入框 事件获取属性
2009/03/26 Javascript
层序遍历在ExtJs的TreePanel中的应用
2009/10/16 Javascript
高性能WEB开发 flush让页面分块,逐步呈现 flush让页面分块,逐步呈现
2010/06/19 Javascript
js采用concat和sort将N个数组拼接起来的方法
2016/01/21 Javascript
jQuery ajax时间差导致的变量赋值问题分析
2016/01/22 Javascript
理解js回收机制通俗易懂版
2016/02/29 Javascript
js实现String.Fomat的实例代码
2016/09/02 Javascript
使用jQuery ajaxupload插件实现无刷新上传文件
2017/04/23 jQuery
angularjs2中父子组件的数据传递的实例代码
2017/07/05 Javascript
详解Chai.js断言库API中文文档
2018/01/31 Javascript
JavaScript EventEmitter 背后的秘密 完整版
2018/03/29 Javascript
从零开始搭建webpack+react开发环境的详细步骤
2018/05/18 Javascript
JavaScript实现异步图像上传功能
2018/07/12 Javascript
在vue中使用Echarts利用watch做动态数据渲染操作
2020/07/20 Javascript
详解vue中v-on事件监听指令的基本用法
2020/07/22 Javascript
[02:11]2016国际邀请赛中国区预选赛最美TA采访现场玩家
2016/06/28 DOTA
深入Python函数编程的一些特性
2015/04/13 Python
Python环境下搭建属于自己的pip源的教程
2016/05/05 Python
Python如何使用turtle库绘制图形
2020/02/26 Python
python绘制分布折线图的示例
2020/09/24 Python
基于Python模拟浏览器发送http请求
2020/11/06 Python
Python性能测试工具Locust安装及使用
2020/12/01 Python
python中uuid模块实例浅析
2020/12/29 Python
Python绘制词云图之可视化神器pyecharts的方法
2021/02/23 Python
The Hut德国站点:时装、家居用品、美容等
2016/09/23 全球购物
C#笔试题
2015/07/14 面试题
《新型玻璃》教学反思
2014/04/13 职场文书
2014年度安全工作总结
2014/12/04 职场文书
爱国主义电影观后感
2015/06/18 职场文书
2016年度员工工作表现评语
2015/12/02 职场文书
2016年小学教师政治学习心得体会
2016/01/23 职场文书
2019教师的学习计划
2019/06/25 职场文书
【海涛dota解说】DCG联赛第一周 LGD VS DH
2022/04/01 DOTA