ES6的异步终极解决方案分享


Posted in Javascript onJuly 11, 2019

前言

Promise async generator是ES6之后才被提出来的,他们都能够用来解决以前JS异步调用产生的一系列问题,例如大名鼎鼎的回调地狱!!!

什么是回调地狱?

在以前js中,我们是无法知晓一个异步操作是否执行完成,为了在异步操作完成后执行特定的代码,我们需要传入回调函数,请看下面的栗子:

这是一个简单的例子,在请求完成后(可以理解为异步操作)执行特定的代码

//我们需要在请求完成后输出请求完成,请看回调法
 function show(params) {
 request('这是请求参数', () => {
 console.log('请求完成')
 })
 }
 /**
 * 模拟发起一个http请求
 * @param {object} data 请求的参数
 * @param {function} callBack 回调函数
 */
 function request(data, callBack) {
 //下面的定时器模拟请求时间
 setTimeout(data => {
 callBack(data);
 }, 3000);
 }
 show()

一次回调当然简单,如果是在这次请求完成后需要立即发起下一次请求呢?例如需要请求request10次,必须在上次请求完成后才能进行下一次请求,来看看 回调地狱 是怎么样的

//我们需要在请求完成后输出请求完成,请看回调法
 function show(params) {
 request('这是请求参数', () => {
 console.log('请求完成1次')
 request('这是请求参数', () => {
 console.log('请求完成2次')
 request('这是请求参数', () => {
  console.log('请求完成3次')
  request('这是请求参数', () => {
  console.log('请求完成4次')
  request('这是请求参数', () => {
  console.log('请求完成5次')
  //这才第五次.....
  })
  })
 })
 })
 })
 }
 /**
 * 模拟发起一个http请求
 * @param {object} data 请求的参数
 * @param {function} callBack 回调函数
 */
 function request(data, callBack) {
 //下面的定时器模拟请求时间
 setTimeout(data => {
 callBack(data);
 },1000);
 }
 show()

这才第5次回调,但是代码的可读性已经极差了!

让我们先看看 Promise async generator怎么解决这个问题,后面再说其使用方式

首先 Promise 篇

//我们需要在请求完成后输出请求完成,请看回调法
 function show(params) {
 request('这是请求参数').then(
 resolve => {
 console.log('请求完成1次');
 return request('这是请求参数')
 }
 ).then(
 resolve => {
 console.log('请求完成2次');
 return request('这是请求参数')
 }
 ).then(
 resolve => {
 console.log('请求完成3次');
 return request('这是请求参数')
 }
 ).then(
 resolve => {
 console.log('请求完成4次');
 return request('这是请求参数')
 }
 ).then(
 resolve => {
 console.log('请求完成5次');
 return request('这是请求参数')
 }
 )
 }
 /**
 * 模拟发起一个http请求
 * @param {object} data 请求的参数
 * @param {function} callBack 回调函数
 */
 function request(data) {
 return new Promise(
 resolve => {
 //下面的定时器模拟请求时间
 setTimeout(data => {
  resolve(data)
 }, 1000);
 }
 )
 }
 show()

虽然还是很长,但是至少嵌套很少了,可读性也比之前更高

再来看看 async

切记,async必须和Promise配合使用

//我们需要在请求完成后输出请求完成,请看回调法
 async function show(params) {
 let result = await request('这是请求参数')
 console.log('请求完成1次');
 result = await request('这是请求参数')
 console.log('请求完成2次');
 result = await request('这是请求参数')
 console.log('请求完成3次');
 result = await request('这是请求参数')
 console.log('请求完成4次');
 result = await request('这是请求参数')
 console.log('请求完成5次');
 }
 /**
 * 模拟发起一个http请求
 * @param {object} data 请求的参数
 * @param {function} callBack 回调函数
 */
 function request(data) {
 return new Promise(
 resolve => {
 //下面的定时器模拟请求时间
 setTimeout(data => {
  resolve(data)
 }, 1000);
 }
 )
 }
 show()

代码是不是更加简短了?而且看起来和同步一样,事实上,这就是使用同步的方式写异步代码,这代码也是同步执行的

最后看看 generator

//我们需要在请求完成后输出请求完成,请看回调法
 function* show() {
 let a1 = yield request('请求参数', () => {
 console.log('这是第1次调用');
 });
 let a2 = yield request('请求参数', () => {
 console.log('这是第2次调用');
 });
 let a3 = yield request('请求参数', () => {
 console.log('这是第3次调用');
 });
 let a4 = yield request('请求参数', () => {
 console.log('这是第4次调用');
 });
 let a5 = yield request('请求参数', () => {
 console.log('这是第5次调用');
 });
 }
 /**
 * 模拟发起一个http请求
 * @param {object} data 请求的参数
 * @param {function} callBack 回调函数
 */
 function request(data, callBack) {
 //下面的定时器模拟请求时间
 setTimeout(() => {
 callBack(data)
 }, 1000);
 }
 let a = show()
 a.next();
 a.next();
 a.next();
 a.next();
 a.next();

以上是异步编程的ES6解决方案,接下来让我们把这3种方式都详细了解下

一.Promise

关于Promise的一些原型,函数,请移步 官方链接

Promise的中文名,也就是承诺,保证,

大家可以将Promise理解为JS的一个承诺,也就是对异步操作的一个承诺,咱先不管异步操作是否能够执行成功,使用Promise的所有异步操作,JS已经承诺处理了,咱就通过Promise的状态来知晓异步操作的执行结果。

一个 Promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 表示着操作完成,状态成功。
  • rejected: 意味着操作失败。

pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用

上文提到Promise的原型中的函数then,then可以接收2个参数(resolve [,reject])

第一个参数resolve 是对成功的一个处理,类型为Function。你可以在其中做 一些异步成功后的操作

第二个参数reject是对失败的一个处理,类型为Function。你可以在其中做 一些异步失败后的操作

一般情况下then我们只会传一个参数,也就是默认的成功处理,失败处理会使用 catch函数

catch函数只有一个参数,也就是代表失败的reject

来看看then catch的使用案例

function show(params) {
 //正常的请求成功操作
 request('这是请求参数').then(
 resolve => {
 console.log(resolve);
 }
 )
 //在then里面同时做成功和失败的操作
 request('这是请求参数').then(
 resolve => {
 //这儿是成功
 console.log(resolve);
 },
 reject => {
 //这儿是失败
 console.log(reject);
 }
 )
 //正常的请求失败操作
 request('这是请求参数').catch(
 reject => {
 console.log(reject);
 }
 )
 }
 /**
 * 模拟发起一个http请求
 * @param {object} data 请求的参数
 * @param {function} callBack 回调函数
 */
 function request(data) {
 return new Promise(
 (resolve, reject) => {
 //下面的定时器模拟请求时间
 setTimeout(data => {
  // resolve('请求成功')
  reject('请求失败')
 }, 1000);
 }
 )
 }
 show()

使用 Promise 能够使你的异步操作变得更加优雅,可读性也比较高,同时,Promise在ES6的各大插件中也使用的相当广泛,所以掌握 Promise是非常有必要的

二.async / await

想详细了解更多,请移步官方文档

async关键字用来定义一个function,用来标识此函数是一个异步函数

切记 切记 切记, await 关键字仅仅在 async function中有效。如果在 async function函数体外使用 await ,你只会得到一个语法错误SyntaxError

async关键字放在函数的声明之前,例如:

async function test() {
 }
 async () => {

 }
 async data => {

 }
 class Test {
 async show() {
 
 }
 }

无论是普通的函数,Lambda表达式,或是ES6的class,该关键字都是放置在函数的声明之前

在调用声明了async的函数时,会返回一个Promise对象

而await关键字则是放置在异步操作的调用之前,await会使得async函数在执行到异步操作时暂停代码执行,直到异步操作返回的Promise状态更改为 fulfilled 或 rejected,此时代码会继续执行,并自动解析Promise返回的值,无论是成功还是失败,都会解析到await关键字前面定义接收的参数,请看例子:

class Test {
 /**
 * 线程休眠
 * @param {number} timer 休眠毫秒数
 */
 Sleep(timer) {
 return new Promise(
 resolve => {
 setTimeout(() => {
  resolve(timer)
 }, timer);
 }
 )
 }
}
let T = new Test();
async function show() {
 console.log('第一次');
 await T.Sleep(1000)
 console.log('第二次');
}
show()

上面的实例调用show函数,会立即打印出 第一次,延时1000毫秒后,会打印出 第二次

原理嘛,就是模仿Java的线程休眠函数Sleep

在打印了 第一次 后,会调用T的Sleep函数,Sleep函数返回了一个Promise,在定时器执行完毕后调用Promise的resolve函数,会将Promise 的状态更改为 fulfilled,此时await检测到Promise的状态更改,继续执行代码,输出 第二次

三 . generator

generator(生成器)是ES6标准引入的新的数据类型,使用方式是在函数名前加上*,generator和普通的函数差不多,但是可以返回多次。

嗯,所有的函数都有默认的返回值,如果没有明确定义,那就会返回undefined,generator也不例外!

generator使用yield关键字来中途返回值,请看案例

function* a(num) {
 let sum = yield num + 1
 console.log(sum);//2 此处是next(r.value)传入的值
 let sum2 = yield sum + 2
 }
 let result = a(1);
 let r = result.next()
 console.log(r);//此处返回第一次yield的值 2
 console.log(result.next(2));//此处返回第二次yield的值 4
 console.log(result.next());//此处并没有第三次yield

ES6的异步终极解决方案分享

第一次输出的是第一次yield的值,此时num为调用a函数时传入的值 1,yield返回了num+1,所以第一行打印的对象 value值是 2

第二次打印的是sum值,也就是第一个yield关键字前面接收的值,此值是下面result.next(2)传入的 ,next函数传入的参数,会赋值到相应的yield关键字左边接收的那个变量,在上方案例,也就是sum变量

第三次输出的是a函数中第二个yield返回的值,此时sum为第一次next(2)传入的2,所以此次返回的值是2+2,所以值也就是 4

第四次输出的是最后一个next(),但是上方generator并没有相应的yield返回,所以此时的value为undefined

yield返回的值是一个对象,其中有done和value两个属性,

  • done 表示该generator是否执行完毕,当没有yield返回时,done的值为true,也就是代表当前generator执行完毕
  • value表示此次yield关键字右方表达式返回的值,当没有yield时,value为undefined

generator是支持迭代器操作的,例:

function* a(num) {
 let sum = yield num + 1
 console.log(sum);//2 此处是next(r.value)传入的值
 let sum2 = yield sum + 2
 }
 let result = a(1);
 for (const key of result) {
 console.log(key);
 
 }

ES6的异步终极解决方案分享

事实证明generator是实现了迭代器的接口的!

嗯,关于generator的实际应用场景,我是没有遇见的,不过听说 async/await是generator的语法糖??

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript中的Array对象使用说明
Jan 17 Javascript
jquery封装的对话框简单实现
Jul 21 Javascript
在IE8上JS实现combobox支持拼音检索功能
May 23 Javascript
angular实现商品筛选功能
Feb 01 Javascript
js中如何完美的解析数据
Mar 18 Javascript
vue组件实现弹出框点击显示隐藏效果
Oct 26 Javascript
vue实现添加与删除图书功能
Oct 07 Javascript
基于javascript的拖拽类封装详解
Apr 19 Javascript
微信小程序网络层封装的实现(promise, 登录锁)
May 08 Javascript
layer更改皮肤的实现方法
Sep 11 Javascript
小程序如何定位所在城市及发起周边搜索
Feb 11 Javascript
vue项目配置使用flow类型检查的步骤
Mar 18 Javascript
微信小程序的开发范式BeautyWe.js入门详解
Jul 10 #Javascript
实现一个 Vue 吸顶锚点组件方法
Jul 10 #Javascript
vue webpack重写cookie路径的方法
Jul 10 #Javascript
vue登录页面cookie的使用及页面跳转代码
Jul 10 #Javascript
Laravel admin实现消息提醒、播放音频功能
Jul 10 #Javascript
微信小程序把百度地图坐标转换成腾讯地图坐标过程详解
Jul 10 #Javascript
JavaScript实现的弹出遮罩层特效经典示例【基于jQuery】
Jul 10 #jQuery
You might like
一个简单的域名注册情况查询程序
2006/10/09 PHP
thinkPHP中多维数组的遍历方法
2016/01/09 PHP
PHP实现基本留言板功能原理与步骤详解
2020/03/26 PHP
YII2框架中添加自定义模块的方法实例分析
2020/03/18 PHP
Ucren Virtual Desktop V2.0
2006/11/07 Javascript
json简单介绍
2008/06/10 Javascript
javaScript 读取和设置文档元素的样式属性
2009/04/14 Javascript
JavaScript面向对象设计二 构造函数模式
2011/12/20 Javascript
JavaScript实现拼音排序的方法
2012/11/20 Javascript
js控制多图左右滚动切换效果代码分享
2015/08/26 Javascript
基于JavaScript实现的折半查找算法示例
2017/04/14 Javascript
JavaScript中undefined和null的区别
2017/05/03 Javascript
jQuery中 DOM节点操作方法大全
2017/10/12 jQuery
微信小程序实现天气预报功能
2018/07/18 Javascript
Vue中 axios delete请求参数操作
2020/08/25 Javascript
Openlayers绘制聚合标注
2020/09/28 Javascript
使用PDB模式调试Python程序介绍
2015/04/05 Python
Windows环境下python环境安装使用图文教程
2018/03/13 Python
python模块smtplib学习
2018/05/22 Python
Laravel框架表单验证格式化输出的方法
2019/09/25 Python
python 监测内存和cpu的使用率实例
2019/11/28 Python
wxpython多线程防假死与线程间传递消息实例详解
2019/12/13 Python
django自带的权限管理Permission用法说明
2020/05/13 Python
html5中监听canvas内部元素点击事件的三种方法
2019/04/28 HTML / CSS
KELLER SPORTS荷兰:在线订购最好的运动产品
2020/10/13 全球购物
2014自荐信的写作技巧
2014/01/28 职场文书
机关工会开展学习雷锋活动总结
2014/03/01 职场文书
幼儿园中班下学期评语
2014/04/18 职场文书
我的教育故事演讲稿
2014/05/04 职场文书
四风批评与自我批评范文
2014/10/14 职场文书
2015年植树节活动总结
2015/02/06 职场文书
工伤劳动仲裁代理词
2015/05/25 职场文书
创业计划书之家教托管
2019/09/25 职场文书
Nginx优化服务之网页压缩的实现方法
2021/03/31 Servers
用React Native制作一个简单的游戏引擎
2021/05/27 Javascript
Android自定义ScrollView实现阻尼回弹
2022/04/01 Java/Android