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 相关文章推荐
JS控制显示隐藏兼容问题(IE6、IE7、IE8)
Apr 01 Javascript
js 表单提交后按钮变灰的实例代码
Aug 16 Javascript
基于jquery实现图片相关操作(重绘、获取尺寸、调整大小、缩放)
Dec 25 Javascript
页面向下滚动ajax获取数据的实现方法(兼容手机)
May 24 Javascript
WebPack基础知识详解
Jan 16 Javascript
微信小程序 引入es6 promise
Apr 12 Javascript
深入浅析Vue不同场景下组件间的数据交流
Aug 15 Javascript
基于 Vue 实现一个酷炫的 menu插件
Nov 14 Javascript
基于 Immutable.js 实现撤销重做功能的实例代码
Mar 01 Javascript
Angular Excel 导入与导出的实现代码
Apr 17 Javascript
Node.js爬虫如何获取天气和每日问候详解
Aug 26 Javascript
vue2实现provide inject传递响应式
May 21 Vue.js
微信小程序的开发范式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
php通用防注入程序 推荐
2011/02/26 PHP
浅谈Eclipse PDT调试PHP程序
2014/06/09 PHP
Yii使用find findAll查找出指定字段的实现方法
2014/09/05 PHP
PHP准确取得服务器IP地址的方法
2015/06/02 PHP
详解php 使用Callable Closure强制指定回调类型
2017/10/26 PHP
PHP htmlspecialchars() 函数实例代码及用法大全
2018/09/18 PHP
PHP中抽象类,接口功能、定义方法示例
2019/02/26 PHP
很多人都是用下面的js刷新站IP和PV
2008/09/05 Javascript
google地图的路线实现代码
2009/08/20 Javascript
jquery.bgiframe.js在IE9下提示INVALID_CHARACTER_ERR错误
2013/01/11 Javascript
jquery实现checkbox 全选/全不选的通用写法
2014/02/22 Javascript
jQuery如何将选中的对象转化为原始的DOM对象
2014/06/09 Javascript
jquery实现增加删除行的方法
2015/02/03 Javascript
javascript中返回顶部按钮的实现
2015/05/05 Javascript
wap手机端解决返回上一页的js实例
2016/12/08 Javascript
详解如何使用webpack打包Vue工程
2017/05/27 Javascript
Underscore之Array_动力节点Java学院整理
2017/07/10 Javascript
利用HBuilder打包前端开发webapp为apk的方法
2017/11/13 Javascript
Vue2.0实现组件数据的双向绑定问题
2018/03/06 Javascript
详解webpack-dev-server使用方法
2018/09/14 Javascript
解决微信小程序云开发中获取数据库的内容为空的方法
2019/05/15 Javascript
[01:00:06]加油DOTA_EP01_网络版
2014/08/09 DOTA
使用python绘制人人网好友关系图示例
2014/04/01 Python
从零学Python之入门(五)缩进和选择
2014/05/27 Python
python中assert用法实例分析
2015/04/30 Python
Python实现控制台进度条功能
2016/01/04 Python
使用python编写监听端
2018/04/12 Python
Python实现打砖块小游戏代码实例
2019/05/18 Python
日本食品网上商店:JaponShop.com
2017/11/28 全球购物
英国文胸专家:AmpleBosom.com
2018/02/06 全球购物
焦裕禄精神心得体会
2014/09/02 职场文书
法人授权委托书范本
2014/09/17 职场文书
2014年乡镇领导个人整改措施
2014/09/19 职场文书
主持人开幕词
2015/01/29 职场文书
2015年资料员工作总结
2015/04/25 职场文书
一次项目中Thinkphp绕过禁用函数的实战记录
2021/11/17 PHP