消除异步传染性 [2023-03-04]
问题
异步传染性
当函数内使用 await 时, 函数上必须写上 async, 后续调用这个函数的都需要加上 async/await, 在函数式编程中都变成副作用了(异步), 所以探索怎么消除异步
ts
async function request() {
return await fetch('/config.js').then(res => res.text())
}
async function m1() {
// other works
return await request()
}
async function m2() {
// other works
return await m1()
}
async function main() {
const res = await m2()
console.log(res)
}解决方式
- 原始流程:
graph LR
A(函数开始) --- B[/fetch/]
B --- E[data]
E --- F(函数结束)
- 报错流程:flowchart TB subgraph 第一次调用 direction LR A(函数开始) --- B[/fetch/] B --- C[data] C --- D[缓存data] D --- E[引发错误] end subgraph 第二次调用 direction LR G(函数开始) G --- H[/fetch/] H --- I[交付data] I --- J[函数继续] J --- K(函数结束) end 第一次调用 --> 第二次调用
取消异步, 然后同步调用, 利用报错实现(粗略代码)
ts
function request() {
return fetch('/config.js')
}
function m1() {
// other works
return request()
}
function main() {
const text = m1()
console.log('[ text ]', text) // 控制台可以看到
}
function run(func: Function) {
type Cache = {
status: 'fulfilled' | 'pending' | 'rejected',
data?: any,
err?: any,
}
// 保留原始fetch
const _fetch = window.fetch
// fetch 缓存返回结果
let cache = [] as Cache[]
let i = 0 // 这次调用期间是第几次调用
window.fetch = (...args) => {
if (cache[i]) {
// 如果有缓存
if (cache[i].status === 'fulfilled') {
// 如果完成,交付data
return cache[i].data
} else if (cache[i].status === 'rejected') {
// 如果失败,抛出err
throw cache[i].err
}
}
// 如果没有,定义缓存
const result: Cache = {
status: 'pending',
data: null,
err: null,
}
cache[i++] = result
// 发送请求
const promise = _fetch(...args)
.then(res => res.text())
.then(data => {
result.status = 'fulfilled'
result.data = data
}, err => {
result.status = 'rejected'
result.err = err
})
//手动抛出promise,然后在调用func中try/catch,判断是否是抛出的promise
throw promise
}
try {
func()
} catch (error) {
// 接收到手动抛出的promise的错误之后,重新执行函数, i归零以命中缓存, 拿到缓存结果
if (error instanceof Promise) {
const reRun = () => {
i = 0
func()
}
error.then(reRun, reRun)
}
}
}
run(main)个人理解
TIP
通过letAsync2SyncInFn, 内部修改doSomething调用的fn函数, 使其调用时, 调用的是letAsync2SyncInFn内部创建的异步函数, 又通过 throw 和 缓存数据, 使得doSomething中可以同步调用fn函数并拿到结果
ts
type CacheI = {
status: 'fulfilled' | 'pending' | 'rejected',
data?: any,
err?: any,
}
let fn = () => Promise.resolve(1)
const doSomething = () => {
const data = fn()
console.log('[ data ]', data) // {status: 'fulfilled', data: 1}
}
const letAsync2SyncInFn = (func: Function) => {
const _fn = fn
let cache!: CacheI
fn = (...args) => {
if (cache) {
if (cache.status === 'fulfilled') {
return cache
} else if (cache.status === 'rejected') {
throw cache
}
}
cache = { status: 'pending' }
const promise = _fn(...args).then(data => {
cache.data = data
cache.status = 'fulfilled'
}, err => {
cache.err = err
cache.status = 'rejected'
})
throw promise
}
try {
func()
} catch (error) {
if (error instanceof Promise) {
error.then(func, func)
}
}
}
letAsync2SyncInFn(doSomething)