再谈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获取图片大小的函数代码
Sep 20 Javascript
复杂的javascript窗口分帧解析
Feb 19 Javascript
Vue中fragment.js使用方法详解
Mar 09 Javascript
基于EasyUI的基础之上实现树形功能菜单
Jun 28 Javascript
手把手教你使用vue-cli脚手架(图文解析)
Nov 08 Javascript
Vuejs 2.0 子组件访问/调用父组件的方法(示例代码)
Feb 08 Javascript
vue.js移动数组位置,同时更新视图的方法
Mar 08 Javascript
浅析vue中的MVVM实现原理
Mar 04 Javascript
vue计算属性无法监听到数组内部变化的解决方案
Nov 06 Javascript
vue 接口请求地址前缀本地开发和线上开发设置方式
Aug 13 Javascript
React实现全选功能
Aug 25 Javascript
js实现扫雷源代码
Nov 27 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使用curl模拟登录后采集页面的例子
2013/11/04 PHP
PHP获取当前完整URL地址的函数
2014/12/21 PHP
JQuery中的ready函数冲突的解决方法
2010/05/17 Javascript
nodejs之请求路由概述
2014/07/05 NodeJs
jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween
2015/12/03 Javascript
浅谈AngularJs指令之scope属性详解
2016/10/24 Javascript
Vue.js实现多条件筛选、搜索、排序及分页的表格功能
2020/11/24 Javascript
angularJS之$http:与服务器交互示例
2017/03/17 Javascript
js精确的加减乘除实例
2017/11/14 Javascript
基于模板引擎Jade的应用(详解)
2017/12/12 Javascript
vue.js 底部导航栏 一级路由显示 子路由不显示的解决方法
2018/03/09 Javascript
jQuery实现简单复制json对象和json对象集合操作示例
2018/07/09 jQuery
Angular路由ui-router配置详解
2018/08/01 Javascript
vue elementUI tree树形控件获取父节点ID的实例
2018/09/12 Javascript
微信小程序模板消息推送的两种实现方式
2019/08/27 Javascript
vue 解决路由只变化参数页面组件不更新问题
2019/11/05 Javascript
vue项目实现图片上传功能
2019/12/23 Javascript
Vue 解决路由过渡动画抖动问题(实例详解)
2020/01/05 Javascript
vue实现登录拦截
2020/06/29 Javascript
vue中的v-model原理,与组件自定义v-model详解
2020/08/04 Javascript
vue-router定义元信息meta操作
2020/12/07 Vue.js
原生JS运动实现轮播图
2021/01/02 Javascript
pycharm 使用心得(三)Hello world!
2014/06/05 Python
Python获取DLL和EXE文件版本号的方法
2015/03/10 Python
对Python 内建函数和保留字详解
2018/10/15 Python
10 行Python 代码实现 AI 目标检测技术【推荐】
2019/06/14 Python
python通过http下载文件的方法详解
2019/07/26 Python
Python一键查找iOS项目中未使用的图片、音频、视频资源
2019/08/12 Python
Python猴子补丁知识点总结
2020/01/05 Python
利用Storage Event实现页面间通信的示例代码
2018/07/26 HTML / CSS
世界排名第一的万圣节服装店:Spirit Halloween
2018/10/16 全球购物
美国综合购物商城:UnbeatableSale.com
2018/11/28 全球购物
大四学生毕业自荐信
2013/11/07 职场文书
保密普查工作实施方案
2014/02/25 职场文书
我爱我家教学反思
2014/05/01 职场文书
Python实现byte转integer
2021/06/03 Python