【JS】993- JavaScript 异步流程控制

2024-03-08 16:10

本文主要是介绍【JS】993- JavaScript 异步流程控制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

来源 | https://github.com/cheogo/learn-javascript

本文主要讲解 JavaScript 在异步流程控制中的一些实践、容错以及复杂异步环境下我们该如何去处理。

发展历史

简要的提及一下,异步流程控制的发展历史大概是 callback hell => Promise => Generator => async/await

ES6 中 Promise 是通过 .then().then().catch() 的方式来解决 callback 多层嵌套的问题。但 Promise 依然是异步执行的,这时候 TJ 的 co,通过 Generator 实现了异步代码的同步化。这个模式和 ES7 中的 async/await 类似。

function A() {// async get dataAfunction B(dataA) {// async get dataBfunction C(dataB) {}}
}Promise(A).then(B).then(C).catch(err => console.log(err))co(function *() {var dataA = yield A()var dataB = yield B(dataA)var dataC = yield C(dataB)
})async () => {const dataA = await A()const dataB = await B(dataA)const dataC = await C(dataB)
}
使用

首先是语法糖支持情况,你可以使用下面命令行查看当前 node 版本对于 ES6/ES7 的支持。

目前大多浏览器是不支持新语法的,如果你当前环境不支持新语法,你可以使用 bable、 co、 Promise、 bluebird 等开源项目来扩展功能。

$ node --v8-options | grep harmony

对了如果你还对这些新语法的使用方式和 API 陌生的话,建议看看 《ECMAScript 6 入门》 这本书。

下面的内容,假定你对基本的使用已经有所了解,我们开始正篇。

Promise 实践和容错

之前当面试官的时候,如果面试对象经常使用 ES6,我会喜欢问一个问题:假设你的移动端页面上有头部、中部、底部三部分数据需要并发的去请求 api 拿到返回数据,你会怎么处理?用 Promise 如何实现?如果其中一个 API 出了错误怎么容错?

1.第一个问题很简单,依次执行三个异步请求函数,在获取到数据后执行渲染函数填充到页面上

2.第二个问题,其实也没多绕,你可以同时执行三个 Promise 函数,也可以打包成 Promise.all() 一并执行,显然对于这种并发执行的异步函数 Promise.all() 更符合程序设计。

const render = log = console.log
const asyncApi = (num) => {return new Promise((resolve, reject) => {setTimeout(() => {if (typeof num !== 'number') {reject('param error')}num += 10resolve(num)}, 100);})
}asyncApi(0).then(render).catch(log)  // 10
asyncApi(5).then(render).catch(log)  // 15
asyncApi(10).then(render).catch(log) // 20Promise.all([asyncApi(0), asyncApi(5), asyncApi(10)]).then(render).catch(log) // [ 10, 15, 20 ]

3.无论怎样,我会把面试者引导到 Promise.all() 上,这时候我会抛出问题 如果其中一个 API 出了错误怎么容错?

asyncApi(0).then(render).catch(log)       // 10
asyncApi(false).then(render).catch(log)   // param error
asyncApi(10).then(render).catch(log)      // 20Promise.all([asyncApi(0), asyncApi(false), asyncApi(10)]).then(render).catch(log) // param error

对比发现,Promise 之间互不影响。但由于 Promise.all() 其实是将传入的多个 Promise 打包成一个,任何一个地方出错了都会直接抛出异常,导致不执行 then 直接跳到了 catch,丢失了成功的数据。

4.解决方式是使用 resolve 传递错误,then 环节去处理

const render = log = console.log
const asyncApi = (num) => {return new Promise((resolve, reject) => {setTimeout(() => {if (typeof num !== 'number') {resolve({ err: 'param error' }) // 修改前:reject('param error')}num += 10resolve({ data: num }) // 修改前:resolve(num)}, 100);})
}Promise.all([asyncApi(0), asyncApi(false), asyncApi(10)]).then(render).catch(log) 
// [ { data: 10 }, { err: 'param error' }, { data: 20 } ],这时候就可以区分处理了
复杂环境

我们假设一个如下的复杂场景,异步请求之间相互依赖。仅仅用 Promise 来实现,会不停的调用 then、 return 并且创建匿名函数。

// 流程示意图
//          data            data1
// asyncApi -----> asyncApi -----> render/error
//     10 + data            data2           data3
//          -----> asyncApi -----> asyncApi -----> render/errorasyncApi(0).then(data => {return Promise.all([asyncApi(data.data), asyncApi(10 + data.data)])
}).then(([data1, data2]) => {render(data1)return asyncApi(data2.data)
}).then(render).catch(log)

而如果加上 async/await 来改写它,就可以完全按同步的写法来获取异步数据,并且语义清晰。

const run = async () => {let data = await asyncApi(0)let [data1, data2] = await Promise.all([asyncApi(data.data), asyncApi(10 + data.data)])render(data1)let data3 = await asyncApi(data2.data)render(data3)
}
run().catch(log)

或许你觉得差不了太多,那我再改一下,现在我们看到 data3 是需要 data2 作为函数参数才能获取,假如:获取 data3 需要 data 和 data2 呢?

你会发现 Promise 的写法隔离了环境,如果需要 data 这个值,那就要想办法传递下去或保存到其他地方,而 async/await 的写法就不需要考虑这个问题。

总结

在本文的前半部分简单介绍了流程控制的发展历史和如何使用这些新的语法糖,后半部分我们聊到了 Promise 和 async/await 如何去实现复杂的异步流程环境,并满足容错和可读性。

做一个有追求的程序员,在实际项目中多去思考容错和可读性,相信代码质量会有不错的提升。

1. JavaScript 重温系列(22篇全)

2. ECMAScript 重温系列(10篇全)

3. JavaScript设计模式 重温系列(9篇全)

4. 正则 / 框架 / 算法等 重温系列(16篇全)

5. Webpack4 入门(上)|| Webpack4 入门(下)

6. MobX 入门(上) ||  MobX 入门(下)

7. 120+篇原创系列汇总

回复“加群”与大佬们一起交流学习~

点击“阅读原文”查看 120+ 篇原创文章

这篇关于【JS】993- JavaScript 异步流程控制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/787647

相关文章

Spring Boot @RestControllerAdvice全局异常处理最佳实践

《SpringBoot@RestControllerAdvice全局异常处理最佳实践》本文详解SpringBoot中通过@RestControllerAdvice实现全局异常处理,强调代码复用、统... 目录前言一、为什么要使用全局异常处理?二、核心注解解析1. @RestControllerAdvice2

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Spring事务传播机制最佳实践

《Spring事务传播机制最佳实践》Spring的事务传播机制为我们提供了优雅的解决方案,本文将带您深入理解这一机制,掌握不同场景下的最佳实践,感兴趣的朋友一起看看吧... 目录1. 什么是事务传播行为2. Spring支持的七种事务传播行为2.1 REQUIRED(默认)2.2 SUPPORTS2

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操