再谈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 简单验证代码(用户名,密码,邮箱)
Sep 28 Javascript
JS中的构造函数详细解析
Mar 10 Javascript
JavaScript中的对象序列化介绍
Dec 30 Javascript
JavaScript实现当网页加载完成后执行指定函数的方法
Mar 21 Javascript
EasyUI创建对话框的两种方式
Aug 23 Javascript
JS实现简单易用的手机端浮动窗口显示效果
Sep 07 Javascript
浅谈js中几种实用的跨域方法原理详解
Dec 02 Javascript
简单的Vue SSR的示例代码
Jan 12 Javascript
关于Vue Router中路由守卫的应用及在全局导航守卫中检查元字段的方法
Dec 09 Javascript
JavaScript原型对象原理与应用分析
Dec 27 Javascript
JS中创建自定义类型的常用模式总结【工厂模式,构造函数模式,原型模式,动态原型模式等】
Jan 19 Javascript
微信小程序实现多行文字超出部分省略号显示功能
Oct 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中用文本文件做数据库的实现方法
2008/03/27 PHP
PHP生成二维码的两个方法和实例
2014/07/01 PHP
PHP获取网站中各文章的第一张图片的代码示例
2016/05/20 PHP
thinkphp框架使用JWTtoken的方法详解
2019/10/10 PHP
JS 自动完成 AutoComplete(Ajax 查询)
2009/07/07 Javascript
JQuery的html(data)方法与<script>脚本块的解决方法
2010/03/09 Javascript
基于jquery的商品展示放大镜
2010/08/07 Javascript
JS小功能(setInterval实现图片效果显示时间)实例代码
2013/11/28 Javascript
JS之Date对象和获取系统当前时间详解
2014/01/13 Javascript
js模仿php中strtotime()与date()函数实现方法
2015/08/11 Javascript
浅析Node.js 中 Stream API 的使用
2015/10/23 Javascript
jquery实现输入框实时输入触发事件代码
2016/12/21 Javascript
easyui简介_动力节点Java学院整理
2017/07/14 Javascript
Vue-Quill-Editor富文本编辑器的使用教程
2018/09/21 Javascript
.netcore+vue 实现压缩文件下载功能
2020/09/24 Javascript
python使用xmlrpc实例讲解
2013/12/17 Python
9种python web 程序的部署方式小结
2014/06/30 Python
使用Python脚本来获取Cisco设备信息的示例
2015/05/04 Python
python更改已存在excel文件的方法
2018/05/03 Python
详解Python的hasattr() getattr() setattr() 函数使用方法
2018/07/09 Python
Python的iOS自动化打包实例代码
2018/11/22 Python
Python脚本利用adb进行手机控制的方法
2019/07/08 Python
Python 画出来六维图
2019/07/26 Python
Python基于BeautifulSoup和requests实现的爬虫功能示例
2019/08/02 Python
python requests.get带header
2020/05/05 Python
利用OpenCV中对图像数据进行64F和8U转换的方式
2020/06/03 Python
python将字典内容写入json文件的实例代码
2020/08/12 Python
详解win10下pytorch-gpu安装以及CUDA详细安装过程
2021/01/28 Python
加拿大最大的相机店:Henry’s
2017/05/17 全球购物
Dr. Martens马汀博士德国官网:马丁靴鼻祖
2019/12/26 全球购物
2014年应届大学生自我评价
2014/01/09 职场文书
个性发展自我评价
2014/02/11 职场文书
《母亲的恩情》教学反思
2014/02/13 职场文书
2014年祖国生日寄语
2014/09/19 职场文书
酒店保洁员岗位职责
2015/02/26 职场文书
银行催款通知书
2015/04/17 职场文书