再谈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 ajax post提交数据乱码
Nov 05 Javascript
JavaScript实现的encode64加密算法实例分析
Apr 15 Javascript
JS非Alert实现网页右下角“未读信息”效果弹窗
Sep 26 Javascript
EasyUI布局 高度自适应
Jun 04 Javascript
基于touch.js手势库+zepto.js插件开发图片查看器(滑动、缩放、双击缩放)
Nov 17 Javascript
JS实现标签页切换效果
May 04 Javascript
React从react-router路由上做登陆验证控制的方法
May 10 Javascript
Vue多系统切换实现方案
Jun 05 Javascript
微信小程序实现页面跳转传递参数(实体,对象)
Aug 12 Javascript
vue 兄弟组件的信息传递的方法实例详解
Aug 30 Javascript
d3.js实现图形缩放平移
Dec 19 Javascript
vue中的计算属性和侦听属性
Nov 06 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
我的论坛源代码(五)
2006/10/09 PHP
php面向对象中static静态属性和静态方法的调用
2015/02/08 PHP
PHP 7.0.2 正式版发布
2016/01/08 PHP
解读PHP中上传文件的处理问题
2016/05/29 PHP
jQuery 源码分析笔记(2) 变量列表
2011/05/28 Javascript
php对mongodb的扩展(小试牛刀)
2012/11/11 Javascript
jQuery实现可拖动的浮动层完整代码
2013/05/27 Javascript
js购物车实现思路及代码(个人感觉不错)
2013/12/23 Javascript
jQuery事件之键盘事件(ctrl+Enter回车键提交表单等)
2014/05/11 Javascript
JavaScript中string转换成number介绍
2014/12/31 Javascript
ajax+jQuery实现级联显示地址的方法
2015/05/06 Javascript
使用Ajax与服务器(JSON)通信实例
2016/11/04 Javascript
JS去除字符串中空格的方法
2017/02/14 Javascript
MUI  Scroll插件的使用详解
2017/04/13 Javascript
es6在react中的应用代码解析
2017/11/08 Javascript
如何从零开始利用js手写一个Promise库详解
2018/04/19 Javascript
layui 监听表格复选框选中值的方法
2018/08/15 Javascript
Nodejs实现微信分账的示例代码
2021/01/19 NodeJs
在python中利用最小二乘拟合二次抛物线函数的方法
2018/12/29 Python
详解python数据结构和算法
2019/04/18 Python
浅谈Django中view对数据库的调用方法
2019/07/18 Python
python分布式编程实现过程解析
2019/11/08 Python
python中前缀运算符 *和 **的用法示例详解
2020/05/28 Python
matplotlib图例legend语法及设置的方法
2020/07/28 Python
Ralph Lauren拉夫·劳伦美国官网:带有浓郁美国气息的高品味时装品牌
2017/11/01 全球购物
高品质和独特的产品世界:Creations and Collections
2018/01/07 全球购物
Desigual英国官网:在线购买原创服装
2018/03/09 全球购物
高中政治教学反思
2014/01/18 职场文书
简单租房协议书
2014/04/09 职场文书
优秀团员自我评价范文
2014/04/23 职场文书
幼儿教师演讲稿
2014/05/06 职场文书
小区文明倡议书
2014/05/16 职场文书
小学亲子活动总结
2014/07/01 职场文书
2015年小班保育员工作总结
2015/05/27 职场文书
寻找最美乡村教师观后感
2015/06/18 职场文书
Android Gradle 插件自定义Plugin实现注意事项
2022/06/16 Java/Android