再谈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 相关文章推荐
Extjs中通过Tree加载右侧TabPanel具体实现
May 05 Javascript
一个js导致的jquery失效问题的解决方法
Nov 27 Javascript
javascript模拟订火车票和退票示例
Apr 24 Javascript
JS实现的用来对比两个用指定分隔符分割的字符串是否相同
Sep 19 Javascript
JavaScript中setUTCMilliseconds()方法的使用详解
Jun 12 Javascript
jQuery Ajax使用FormData对象上传文件的方法
Sep 07 Javascript
详解Angular Reactive Form 表单验证
Jul 06 Javascript
vue mint-ui 实现省市区街道4级联动示例(仿淘宝京东收货地址4级联动)
Oct 16 Javascript
AngularJS集合数据遍历显示的实例
Dec 27 Javascript
Vue.js 表单控件操作小结
Mar 29 Javascript
详解ES6中的 Set Map 数据结构学习总结
Nov 06 Javascript
原生js实现自定义消息提示框
Nov 19 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&MYSQL分页原理及实现
2007/01/02 PHP
php实现的通用图片处理类
2015/03/24 PHP
PHP中isset与array_key_exists的区别实例分析
2015/06/02 PHP
php微信公众号js-sdk开发应用
2016/11/28 PHP
PHP中cookie知识点学习
2018/05/06 PHP
laravel邮件发送的实现代码示例
2020/01/31 PHP
JS中==与===操作符的比较
2009/03/21 Javascript
Google的跟踪代码 动态加载js代码方法应用
2012/11/12 Javascript
jQuery实现倒计时按钮功能代码分享
2014/09/03 Javascript
JavaScript获取图片真实大小代码实例
2014/09/24 Javascript
JS中三目运算符和if else的区别分析与示例
2014/11/21 Javascript
AngularJS过滤器filter用法分析
2016/12/11 Javascript
Javascript计算二维数组重复值示例代码
2016/12/18 Javascript
Vue使用vue-cli创建项目
2017/09/01 Javascript
基于node打包可执行文件工具_Pkg使用心得分享
2018/01/24 Javascript
JavaScript实现一个简易的计算器实例代码
2018/05/10 Javascript
jquery.pagination.js分页使用教程
2018/10/23 jQuery
详解webpack打包后如何调试的方法步骤
2018/11/07 Javascript
vue-router beforeEach跳转路由验证用户登录状态
2018/12/26 Javascript
JavaScript学习笔记之基于定时器实现图片无缝滚动功能详解
2019/01/09 Javascript
vue.js 实现a标签href里添加参数
2019/11/12 Javascript
vue props对象validator自定义函数实例
2019/11/13 Javascript
如何基于javascript实现贪吃蛇游戏
2020/02/09 Javascript
基于原生js实现判断元素是否有指定class名
2020/07/11 Javascript
[01:04:14]OG vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
Python程序中用csv模块来操作csv文件的基本使用教程
2016/03/03 Python
python matplotlib 注释文本箭头简单代码示例
2018/01/08 Python
Python实现针对给定字符串寻找最长非重复子串的方法
2018/04/21 Python
python实现图片识别汽车功能
2018/11/30 Python
Python3的高阶函数map,reduce,filter的示例详解
2019/07/23 Python
Python selenium键盘鼠标事件实现过程详解
2020/07/28 Python
容易被忽略的Python内置类型
2020/09/03 Python
工程项目经理岗位职责
2013/12/15 职场文书
自行车租赁公司创业计划书
2014/01/28 职场文书
2014小学植树节活动总结
2014/03/10 职场文书
2016医师资格考试考生诚信考试承诺书
2016/03/25 职场文书