再谈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获取下拉列表的值为null的解决方法
Mar 18 Javascript
自定义一个jquery插件[鼠标悬浮时候 出现说明label]
Jun 27 Javascript
JS HTML5 音乐天气播放器(Ajax获取天气信息)
May 26 Javascript
jquery原创弹出层折叠效果点击折叠弹出一个层
Mar 12 Javascript
jQuery简单实现中间浮窗效果
Sep 04 Javascript
JavaScript基于对象去除数组重复项的方法
Oct 09 Javascript
ionic进入多级目录后隐藏底部导航栏(tabs)的完美解决方案
Nov 23 Javascript
jQuery validate 验证radio实例
Mar 01 Javascript
微信小程序 如何引入外部字体库iconfont的图标
Jan 31 Javascript
浅谈在vue中用webpack打包之后运行文件的问题以及相关配置方法
Feb 21 Javascript
Angular事件之不同组件间传递数据的方法
Nov 15 Javascript
json 带斜杠时如何解析的实现
Aug 12 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 类型转换函数intval
2009/06/20 PHP
PHP使用Alexa API获取网站的Alexa排名例子
2014/06/12 PHP
对PHP新手的一些建议(PHP学习经验总结)
2014/08/20 PHP
yii去掉必填项中星号的方法
2015/12/28 PHP
总结PHP内存释放以及垃圾回收
2018/03/29 PHP
PHP替换Word中变量并导出PDF图片的实现方法
2020/11/26 PHP
jquery实现显示已选用户
2014/07/21 Javascript
JavaScript 事件绑定及深入
2015/04/13 Javascript
jQuery基于扩展实现的倒计时效果
2016/05/14 Javascript
JQuery动态添加Select的Option元素实现方法
2016/08/29 Javascript
jquery插入兄弟节点的操作方法
2016/12/07 Javascript
Three.js如何实现雾化效果示例代码
2017/09/27 Javascript
VueJs 将接口用webpack代理到本地的方法
2017/11/27 Javascript
微信小程序Echarts覆盖正常组件问题解决
2019/07/13 Javascript
Vue 使用typescript如何优雅的调用swagger API
2020/09/01 Javascript
Python中函数参数设置及使用的学习笔记
2016/05/03 Python
Python正则表达式知识汇总
2017/09/22 Python
python编程实现12306的一个小爬虫实例
2017/12/27 Python
Python进阶之自定义对象实现切片功能
2019/01/07 Python
Python模拟FTP文件服务器的操作方法
2020/02/18 Python
Python多线程多进程实例对比解析
2020/03/12 Python
python topk()函数求最大和最小值实例
2020/04/02 Python
Python 解析库json及jsonpath pickle的实现
2020/08/17 Python
Python 3.9的到来到底是意味着什么
2020/10/14 Python
基于css3的属性transition制作菜单导航效果
2015/09/01 HTML / CSS
阿姆斯特丹城市卡:Amsterdam Pass
2019/12/01 全球购物
美国最大的购物网站:Amazon.com(亚马逊美国)
2020/05/23 全球购物
中学生运动会入场词
2014/02/12 职场文书
志愿者服务感言
2014/02/27 职场文书
座谈会主持词
2014/03/20 职场文书
计算机网络及管理学专业求职信
2014/06/05 职场文书
2014年公务员转正工作总结
2014/11/07 职场文书
时尚女魔头观后感
2015/06/04 职场文书
孟佩杰观后感
2015/06/17 职场文书
Java实现扫雷游戏详细代码讲解
2022/05/25 Java/Android
MySQL中order by的执行过程
2022/06/05 MySQL