再谈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 使用手册(七)
Sep 23 Javascript
jquery 页面滚动到指定DIV实现代码
Sep 25 Javascript
JavaScript中函数表达式和函数声明及函数声明与函数表达式的不同
Nov 15 Javascript
JavaScript Math 对象常用方法总结
Apr 28 Javascript
浅谈js在html中的加载执行顺序,多个jquery ready执行顺序
Nov 26 Javascript
深入了解JavaScript的逻辑运算符(与、或)
Dec 20 Javascript
js原生日历的实例(推荐)
Oct 31 Javascript
关于JavaScript语句后面的分号问题
Dec 07 Javascript
在 Typescript 中使用可被复用的 Vue Mixin功能
Apr 17 Javascript
vue父组件异步获取数据传给子组件的方法
Jul 26 Javascript
vue实现简单的MVVM框架
Aug 05 Javascript
简述ES6新增关键字let与var的区别
Aug 23 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中查询SQL Server或Sybase时TEXT字段被截断的解决方法
2009/03/10 PHP
PHP自定义函数格式化json数据示例
2016/09/14 PHP
php使用str_shuffle()函数生成随机字符串的方法分析
2017/02/17 PHP
Laravel如何友好的修改.env配置文件详解
2017/06/07 PHP
如何让页面在打开时自动刷新一次让图片全部显示
2012/12/17 Javascript
jquery中文乱码的多种解决方法
2013/06/21 Javascript
javascript:FF/Chrome与IE动态加载元素的区别说明
2014/01/26 Javascript
JQuery中serialize()、serializeArray()和param()方法示例介绍
2014/07/31 Javascript
JavaScript通过setTimeout实时显示当前时间的方法
2015/04/16 Javascript
innerHTML中标签可以换行的方法汇总
2015/08/14 Javascript
JavaScript中日期的相关操作方法总结
2015/10/24 Javascript
js图片轮播效果实现代码
2020/04/18 Javascript
基于javascript制作微博发布栏效果
2016/04/04 Javascript
jQuery实现使用sort方法对json数据排序的方法
2018/04/17 jQuery
Angular 封装并发布组件的方法示例
2018/04/19 Javascript
angular 组件通信的几种实现方式
2018/07/13 Javascript
微信小程序如何修改本地缓存key中单个数据的详解
2019/04/26 Javascript
基于javascript实现贪吃蛇经典小游戏
2020/04/10 Javascript
vue.js+ElementUI实现进度条提示密码强度效果
2020/01/18 Javascript
Vue 打包体积优化方案小结
2020/05/20 Javascript
linux 下实现python多版本安装实践
2014/11/18 Python
Python使用设计模式中的责任链模式与迭代器模式的示例
2016/03/02 Python
opencv实现图片模糊和锐化操作
2018/11/19 Python
Python爬虫爬取、解析数据操作示例
2020/03/27 Python
pycharm 多行批量缩进和反向缩进快捷键介绍
2021/01/15 Python
html5 canvas实现给图片添加平铺水印
2019/08/20 HTML / CSS
大学生自我鉴定书
2014/03/24 职场文书
大学生活动总结怎么写
2014/04/29 职场文书
幼儿园中班区域活动总结
2014/07/09 职场文书
支部书记四风问题对照检查材料
2014/10/04 职场文书
音乐教师个人工作总结
2015/02/06 职场文书
管理失职检讨书
2015/05/05 职场文书
2015年党风廉政建设个人总结
2015/08/18 职场文书
小学数学教师研修日志
2015/11/13 职场文书
物业管理交接协议书
2016/03/24 职场文书
2019脱贫攻坚工作总结报告范本!
2019/08/06 职场文书