消除异步的传染性
2023-03-04 Javascript
消除异步的传染性 #
普通代码 #
async function getUser() {
console.log("getUser");
const user = await fetch("https://api.uomg.com/api/rand.qinghua").then(
(res) => res.json()
);
console.log("getUser已获取到用户数据user", user);
return user;
}
async function m1() {
console.log("m1");
return await getUser();
}
async function m2() {
console.log("m2");
return await m1();
}
async function m3() {
console.log("m3");
return await m2();
}
async function main() {
console.log("main");
const user = await m3();
console.log("feng", user);
}
main();
async function getUser() {
console.log("getUser");
const user = await fetch("https://api.uomg.com/api/rand.qinghua").then(
(res) => res.json()
);
console.log("getUser已获取到用户数据user", user);
return user;
}
async function m1() {
console.log("m1");
return await getUser();
}
async function m2() {
console.log("m2");
return await m1();
}
async function m3() {
console.log("m3");
return await m2();
}
async function main() {
console.log("main");
const user = await m3();
console.log("feng", user);
}
main();
异步的传染性: 由于getUser有异步操作,导致后续函数全部都需要改造成async函数。
需求: 让函数直接写成普通同步函数,同时运行结果跟async函数一致。
async函数的特点: 异步操作执行完,才执行后续代码。
我们只需要实现async函数的特点即可完成需求。
改造后的代码 #
function getUser() {
// 测试普通错误
// throw new Error("普通错误");
console.log("getUser");
const user1 = fetch("https://api.uomg.com/api/rand.qinghua");
const user2 = fetch("https://api.uomg.com/api/rand.qinghua");
console.log("getUser已获取到用户数据user1", user1);
console.log("getUser已获取到用户数据user2", user2);
console.log("user1 === user2", user1 === user2);
return user1;
}
function m1() {
console.log("m1");
return getUser();
}
function m2() {
console.log("m2");
return m1();
}
function m3() {
console.log("m3");
return m2();
}
function main() {
console.log("main");
const user = m3();
console.log("feng", user);
}
// run函数为主要代码,该函数会将异步操作进行处理。
function run(fn) {
// 保留原有fetch,发起请求时使用。
const originalFetch = window.fetch;
// 缓存结果
const cache = [];
// 记录fetch调用的顺序,从而缓存多次fetch请求结果。(整条链路上可能有多个fetch调用,需要分别缓存)
let i = 0;
// 改写fetch,当有异步操作时则中断fn执行,异步有结果时,重新执行fn,并将异步结果返回。
window.fetch = (...args) => {
// 命中缓存
if (cache[i]) {
const cacheData = cache[i];
// 使用i++(第一个fetch执行完,才能执行下一个。所以i的变动放在完成后是较好的)
// 第一次fetch未命中缓存,存下标0的位置。第一次fetch有结果,重新执行fn,重置i为0,下标0有结果命中缓存,i++为1。
// 第二次fetch未命中缓存,存下标1的位置。第二次fetch有结果,重新执行fn,重置i为0,下标0有结果命中缓存,下标1有结果命中缓存,i++为2。
i++;
// 判断结果成功与否,从而决定是否抛出错误。
// 不需要判断是否等于pending,promise改变了状态才会重新执行fn。
if (cacheData.status === "fulfilled") {
return cacheData.data;
}
if (cacheData.status === "rejected") {
throw cacheData.err;
}
} else {
// 未命中缓存
const result = {
status: "pending",
data: null,
err: null,
};
cache[i] = result;
throw originalFetch(...args)
.then((res) => res.json())
.then((jsonData) => {
result.status = "fulfilled";
result.data = jsonData;
})
.catch((err) => {
result.status = "rejected";
result.err = err;
});
}
};
const execute = () => {
try {
// i需要重置。因为fn可能是重新执行的,需要准确获取缓存。(改写后的fetch,类似React的hook,用调用顺序记录不同的state)
i = 0;
fn();
} catch (err) {
// 捕获promise
if (err instanceof Promise) {
// 当异步有结果时重新执行fn。
// 重新执行的fn依旧需要try catch进行错误处理,所以不能单独执行fn。
// 注意此处并不是递归,只是不断从微任务队列取任务执行。所以不会栈溢出。但是有可能同一事件循环太多任务,导致页面卡顿。
err.then(execute, execute);
} else {
// 将普通错误抛出去
throw err;
}
}
};
execute();
}
run(main);
function getUser() {
// 测试普通错误
// throw new Error("普通错误");
console.log("getUser");
const user1 = fetch("https://api.uomg.com/api/rand.qinghua");
const user2 = fetch("https://api.uomg.com/api/rand.qinghua");
console.log("getUser已获取到用户数据user1", user1);
console.log("getUser已获取到用户数据user2", user2);
console.log("user1 === user2", user1 === user2);
return user1;
}
function m1() {
console.log("m1");
return getUser();
}
function m2() {
console.log("m2");
return m1();
}
function m3() {
console.log("m3");
return m2();
}
function main() {
console.log("main");
const user = m3();
console.log("feng", user);
}
// run函数为主要代码,该函数会将异步操作进行处理。
function run(fn) {
// 保留原有fetch,发起请求时使用。
const originalFetch = window.fetch;
// 缓存结果
const cache = [];
// 记录fetch调用的顺序,从而缓存多次fetch请求结果。(整条链路上可能有多个fetch调用,需要分别缓存)
let i = 0;
// 改写fetch,当有异步操作时则中断fn执行,异步有结果时,重新执行fn,并将异步结果返回。
window.fetch = (...args) => {
// 命中缓存
if (cache[i]) {
const cacheData = cache[i];
// 使用i++(第一个fetch执行完,才能执行下一个。所以i的变动放在完成后是较好的)
// 第一次fetch未命中缓存,存下标0的位置。第一次fetch有结果,重新执行fn,重置i为0,下标0有结果命中缓存,i++为1。
// 第二次fetch未命中缓存,存下标1的位置。第二次fetch有结果,重新执行fn,重置i为0,下标0有结果命中缓存,下标1有结果命中缓存,i++为2。
i++;
// 判断结果成功与否,从而决定是否抛出错误。
// 不需要判断是否等于pending,promise改变了状态才会重新执行fn。
if (cacheData.status === "fulfilled") {
return cacheData.data;
}
if (cacheData.status === "rejected") {
throw cacheData.err;
}
} else {
// 未命中缓存
const result = {
status: "pending",
data: null,
err: null,
};
cache[i] = result;
throw originalFetch(...args)
.then((res) => res.json())
.then((jsonData) => {
result.status = "fulfilled";
result.data = jsonData;
})
.catch((err) => {
result.status = "rejected";
result.err = err;
});
}
};
const execute = () => {
try {
// i需要重置。因为fn可能是重新执行的,需要准确获取缓存。(改写后的fetch,类似React的hook,用调用顺序记录不同的state)
i = 0;
fn();
} catch (err) {
// 捕获promise
if (err instanceof Promise) {
// 当异步有结果时重新执行fn。
// 重新执行的fn依旧需要try catch进行错误处理,所以不能单独执行fn。
// 注意此处并不是递归,只是不断从微任务队列取任务执行。所以不会栈溢出。但是有可能同一事件循环太多任务,导致页面卡顿。
err.then(execute, execute);
} else {
// 将普通错误抛出去
throw err;
}
}
};
execute();
}
run(main);
为什么不是递归? #
let i = 0;
const fn = () => {
console.log("times:", i++);
// 将fn放到微任务队列中
Promise.resolve().then(fn);
};
fn();
// 流程:
// 1. fn()执行过程中将fn放到微任务队列中
// 2. fn()执行完毕,事件循环从微任务队列取出任务fn执行
// 3. fn()执行,回到第一步。
// 总结:并不是递归。只是不断从微任务队列取任务执行。
let i = 0;
const fn = () => {
console.log("times:", i++);
// 将fn放到微任务队列中
Promise.resolve().then(fn);
};
fn();
// 流程:
// 1. fn()执行过程中将fn放到微任务队列中
// 2. fn()执行完毕,事件循环从微任务队列取出任务fn执行
// 3. fn()执行,回到第一步。
// 总结:并不是递归。只是不断从微任务队列取任务执行。
总结 #
代码整体思路 #
- 函数中有异步操作,使用throw中断函数后续的执行
- 异步操作执行完毕时,缓存异步结果(一个异步操作对应一个缓存)
- 重新执行函数,异步操作直接返回缓存内容
- 怎么重新执行函数?(使用try catch捕获错误,就可以在catch重新执行函数)
- 什么时机重新执行函数?(当异步有结果后,即Promise改变状态后,即可重新执行)
优缺点 #
优点:编写代码时直接编写同步代码即可,不需要使用async、await等
缺点:函数需要多次重复执行,async、await只需要执行一次。假如函数有其他大量计算,将影响性能
共同点:仍然是异步有结果后,才能真正进行下一步操作
完整的html示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
window.asyncFn = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 2000)
})
}
function m1() {
return window.asyncFn()
}
function m2() {
return m1()
}
function m3() {
return m2()
}
function main() {
console.log('11111') // 之前逻辑执行多次
const r = m3()
console.log('2222')
console.log('r', r)
}
function run(func) {
const cache = []
let i = 0
const beforeAsyncFn = window.asyncFn
window.asyncFn = (...args) => {
if (cache[i]) {
if (cache[i].status === 'fulfilled')
return cache[i].data
}
const result = {
status: 'pending',
data: null,
err: null,
}
cache[i++] = result
// 执行异步
const prom = beforeAsyncFn(...args).then((res) => {
result.status = 'fulfilled'
result.data = res
})
throw prom
}
try {
func()
}
catch (e) {
if (e instanceof Promise) {
const reRun = () => {
i = 0
func()
}
e.then(reRun, reRun)
}
}
}
run(main)
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
window.asyncFn = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 2000)
})
}
function m1() {
return window.asyncFn()
}
function m2() {
return m1()
}
function m3() {
return m2()
}
function main() {
console.log('11111') // 之前逻辑执行多次
const r = m3()
console.log('2222')
console.log('r', r)
}
function run(func) {
const cache = []
let i = 0
const beforeAsyncFn = window.asyncFn
window.asyncFn = (...args) => {
if (cache[i]) {
if (cache[i].status === 'fulfilled')
return cache[i].data
}
const result = {
status: 'pending',
data: null,
err: null,
}
cache[i++] = result
// 执行异步
const prom = beforeAsyncFn(...args).then((res) => {
result.status = 'fulfilled'
result.data = res
})
throw prom
}
try {
func()
}
catch (e) {
if (e instanceof Promise) {
const reRun = () => {
i = 0
func()
}
e.then(reRun, reRun)
}
}
}
run(main)
</script>
</body>
</html>
版权属于: vincent
转载时须注明出处及本声明
Tags:# Javascript