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等比例缩放图片控制超出范围的图片
Aug 06 Javascript
jquery实现可拖拽弹出层特效
Jan 04 Javascript
Javascript设计模式之观察者模式的多个实现版本实例
Mar 03 Javascript
学习Bootstrap组件之下拉菜单
Jul 28 Javascript
jQuery实现彩带延伸效果的网页加载条loading动画
Oct 29 Javascript
jQuery解析json数据实例分析
Nov 24 Javascript
javascript中new关键字详解
Dec 14 Javascript
深入理解jquery跨域请求方法
May 18 Javascript
JS轮播图实现简单代码
Feb 19 Javascript
jquery根据name取得select选中的值实例(超简单)
Jan 25 jQuery
何时/使用 Vue3 render 函数的教程详解
Jul 25 Javascript
解决新建一个vue项目过程中遇到的问题
Oct 22 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
一个PHP日历程序
2006/12/06 PHP
PHP开发框架总结收藏
2008/04/24 PHP
文件上传之SWFUpload插件(代码)
2015/07/30 PHP
js类中获取外部函数名的方法
2007/08/19 Javascript
IE和firefox浏览器的event事件兼容性汇总
2009/12/06 Javascript
扩展jquery实现客户端表格的分页、排序功能代码
2011/03/16 Javascript
jquery获取下拉列表的值为null的解决方法
2011/03/18 Javascript
在服务端(Page.Write)调用自定义的JS方法详解
2013/08/09 Javascript
setTimeout()与setInterval()方法区别介绍
2013/12/24 Javascript
验证码在IE中不刷新而谷歌等浏览器正常的解决方案
2014/03/18 Javascript
jQuery分别获取选中的复选框值的示例
2014/06/17 Javascript
js实现卡片式项目管理界面UI设计效果
2015/12/08 Javascript
jqGrid用法汇总(全经典)
2016/06/28 Javascript
TableSort.js表格排序插件使用方法详解
2017/02/10 Javascript
vue2.x select2 指令封装详解
2017/10/12 Javascript
Layui给数据表格动态添加一行并跳转到添加行所在页的方法
2018/08/20 Javascript
新手快速入门JavaScript装饰者模式与AOP
2019/06/24 Javascript
ES6 Promise对象概念及用法实例详解
2019/10/15 Javascript
使用JavaScrip模拟实现仿京东搜索框功能
2019/10/16 Javascript
JavaScript面试中常考的字符串操作方法大全(包含ES6)
2020/05/10 Javascript
Vue中使用wangeditor富文本编辑的问题
2021/02/07 Vue.js
[00:03]DOTA2新版本PA至宝展示
2014/11/19 DOTA
[00:32]2018DOTA2亚洲邀请赛Secret出场
2018/04/03 DOTA
[41:17]VG vs Optic 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
[54:05]DOTA2-DPC中国联赛定级赛 SAG vs iG BO3第一场 1月9日
2021/03/11 DOTA
在Python的Django框架中生成CSV文件的方法
2015/07/22 Python
python对配置文件.ini进行增删改查操作的方法示例
2017/07/28 Python
Python2随机数列生成器简单实例
2017/09/04 Python
pycharm new project变成灰色的解决方法
2019/06/27 Python
python图形开发GUI库pyqt5的基本使用方法详解
2020/02/14 Python
详解django使用include无法跳转的解决方法
2020/03/19 Python
OpenCV 使用imread()函数读取图片的六种正确姿势
2020/07/09 Python
python获取linux系统信息的三种方法
2020/10/14 Python
浅析python实现动态规划背包问题
2020/12/31 Python
MySQL 数据丢失排查案例
2021/05/08 MySQL
MySQL 使用自定义变量进行查询优化
2021/05/14 MySQL