如何消除JS异步传染性?
本帖最后由 bigonion 于 2023-12-14 15:09 编辑# [首页<<](https://bigonion.cn/blog)
# 如何消除JS异步传染性?
## 前言
我们在平时开发的过程中经常会遇到一个棘手的问题,就是大环境下是同步调用的方式,但是有一个外部库使用了链式,所以不得不对整个调用栈都进行链式重写,这就被称之为异步调用的传染性。今天来对消除async和await的传染性做一些探讨
---
## 正文
### 问题的抛出
首先来看一个例子
```js
'use strict';
async function fetchData() {
return await fetch('https://bigonion.cn')
.then(response => response.text())
}
async function main() {
let _data
_data = await fetchData()
console.log(_data.toString().substring(0, 200))
}
main()
```
在这个案例中,我们用fetch来请求网站拿到源码,由于fetch是异步调用,在所有调用了fetchData的函数必须要带上async或者以promise的方式被使用。
那么如果我想让fetchData不带上async和await并能够让`_data`顺利拿到请求的结果怎么办?
直接去掉async await吗?
```js
function fetchData() {
return fetch('https://bigonion.cn')
.then(response => response.text())
}
function main() {
let _data
_data = fetchData()
console.log(_data)
}
main()
```
显然这是不行的,因为这样`_data`拿到的就是一个promise类型,打印出来的结果也是一个promise对象
### 解决
#### 重新设计Fetch函数
这里问题出在fetch是异步上,所以我们需要重新对fetch进行设计,由于涉及到修改fetch,所以我们得在一个闭包里面运行,此闭包里面的fetch是会被修改的,也就是提供一个新的fetch函数。
当然这里实现的fetch比较简陋,不具备then属性,所以调用的时候把处理数据放在`changedFetchEnv`函数中
```js
function fetchData() {
return fetch('https://bigonion.cn')
}
function main() {
let _data
_data = fetchData()
console.log("页面GET到的内容\n:", _data.toString().substring(0, 200))
}
function changedFetchEnv(func){
func()
}
changedFetchEnv(main)
```
下面对fetch进行重写:
1. 设置缓存为一个数组,方便多次fetch请求,i是fetch调用的次数,函数中可能有多次使用fetch,`_fetch`是原`fetch`函数
2. 如果命中缓存,且状态是已经完成,则立即返回缓存的数据,如果是失败状态则返回错误信息。
3. 定义空`result`结构,具有`status、data、err`属性
4. 保存本次结果到`cache`缓存里
5. 利用原`fetch`函数`_fetch`来请求
6. 如果成功,则设定`result.status` 为 "fulfilled" 状态
7. 如果失败,则设定`result.status` 为 "rejected" 状态
8. 同时设定`result.data` 为 请求到的结果
9. 最后利用`throw`方法抛出本次请求的`_promise`对象,来立刻终止异步进程
10. 然后用try catch执行主函数,一旦检测到错误,且类型是promise,则表明没有命中缓存结果需要重新fetch一次,就再次运行主函数,并且清空`i`。
```js
function fetchData() {
return fetch('https://bigonion.cn')
}
function main() {
let _data
_data = fetchData()
console.log("页面GET到的内容\n:", _data.toString().substring(0, 200))
}
function changedFetchEnv(func) {
let cache = []
let i = 0
let _fetch = window.fetch
window.fetch = (...args) => {
if (cache) {
if (cache.status === "fulfilled") {
return cache.data
}
else if (cache.status === "rejected") {
throw cache.err
}
}
const result = {
status: "pending",
data: null,
err: null
}
cache = result
const _promise = _fetch(...args)
.then(res => res.text())
.then(
(res) => {
result.status = "fulfilled"
result.data = res
},
(err) => {
result.status = "rejected"
result.err = err
})
throw _promise
}
try {
func()
} catch (err) {
if (err instanceof Promise) {
i = 0
err.then(func, func);
}
}
}
changedFetchEnv(main)
```
最后我们发现,即使不用async和await关键字,通过异常抛出的方式,我们一样能把异步的函数变成同步的方式去写代码,并且能甩掉async和await关键字,防止了污染调用栈。
对于这种异常抛出的方法,我个人认为还是有非常大的可能性的,的的确确把看起来完全不可能消除的副作用给湮灭了,而且还有很大的可复用性。听说这个方法在React中已经被广泛使用了,具体情况可以看下面视频链接,还有实现的思维导图讲解,说的还是比较详细的。
也希望大家能给出一些建议和意见,对于这种方法,你怎么看?
----
## 参考资料
https://www.bilibili.com/video/BV1Qc411S7wU/?spm_id_from=333.337.search-card.all.click&vd_source=347109678632e4593a175ba64105c5ff
---
## 关于
作者:bigonion
邮箱:bigonion@bigonion.cn
NameSpace: [大聪花的家](https://bigonion.cn)
Origin: [大聪花的博客](https://bigonion.cn/blog)
Powered by (https://md.bigonion.cn)
"2023.12.13"
声明:未经本人同意,禁止转载、搬运、抄袭!
!(data/attachment/forum/202312/14/093742uqm7mb1npp9pqxjr.png)
代码似乎有点问题,无法和其他代码同步了 ggnb,学习到了
主要就是要promise再去执行func,然后再调用的时候有cache,从cache里取结果返回?
不过还有些细节问题,fetch然后业务代码,然后再fetch,那么这中间的业务代码是不是需要幂等
不过开发中倒是没注意过这个,我感觉直接加个await/async的成本比这样小吧 steven026 发表于 2023-12-14 09:38
!(data/attachment/forum/202312/14/093742uqm7mb1npp9pqxjr.png)
代码似乎有点问题,无法 ...
这里应该是没有问题的,gg尝试这样写就能明显发现是顺序执行,结果是在后台请求完成后重写执行main的同步再输出,你也可以在main函数里面加一个 console.log("domain"),就能发现是按照预期执行的了
console.log("12")
changedFetchEnv(main)
console.log("23") 王一之 发表于 2023-12-14 09:58
ggnb,学习到了
主要就是要promise再去执行func,然后再调用的时候有cache,从cache里取结果返回?
业务代码肯定是需要等的,无论是正常异步还是这样异常抛出的方式,只不过这里能消除async的副作用罢了,当函数第一次执行到请求时会立刻终止,然后再次执行拿着缓存结果顺序执行 但是这个也不能叫消除异步,而是等异步出结果后重复执行一遍同步函数,我觉得副作用很大,比方说我在main里面fetch之前执行了很多同步操作,放到changedFetchEnv之后这些同步操作就会被执行2次,不仅有性能问题,还容易引发预期之外的错误:
window.globalTest = 0;
function main() {
window.globalTest++;
if (window.globalTest > 1) {
throw new Error('Unknown');
}
const _data = fetchData();
console.log(_data)
} cxxjackie 发表于 2023-12-14 22:20
但是这个也不能叫消除异步,而是等异步出结果后重复执行一遍同步函数,我觉得副作用很大,比方说我在main里 ...
既然你知道这是重复执行的函数的话,我的理解是可以通过加入对 if( _data) 的判断来避免重复执行,只有当 _data有数据才执行,不就可以避免反复执行了嘛😁 bigonion 发表于 2023-12-15 22:20
既然你知道这是重复执行的函数的话,我的理解是可以通过加入对 if( _data) 的判断来避免重复执行,只有当 ...
fetch之后的代码不会被重复执行(抛出错误强制中止了),只有之前的会,所以判断_data应该是没有意义的。 cxxjackie 发表于 2023-12-15 23:21
fetch之后的代码不会被重复执行(抛出错误强制中止了),只有之前的会,所以判断_data应该是没有意义的。 ...
你说的对,那就需要把获取数据的这些步骤全都写在操作前面来确保执行一次
页:
[1]
2