Skip to content

6.异步传染

什么是异步传染

异步传染是指如果某个函数用了 async,那么调用它的函数想要得到这个函数的返回值,就必须使用 await 关键字, 要使用 await 关键字,就必须在调用它的函数中使用 async 关键字。

举个例子:

javascript
async function foo() {
  return 1;
} 

async function bar() {
  const result = await foo();
  console.log(result);
}

bar(); // 1

bar 函数为了得到 foo 函数的返回值,必须使用 await 关键字。

那么我们有没有什么方式可以用同步的方式调用 foo 函数呢,比如:

javascript
async function foo() {
  return 1;
} 

function bar() {
  const result = foo();
  console.log(result);
}

bar(); // 1

foo 函数的执行编程了同步,那么,能不能在 foo 函数的结果出来的时候,再去调用呢,如果是相同的函数,那肯定是悖论,如果我们重写 foo 函数,那么就有可能。

javascript
async function foo() {
  return 1;
} 

function bar() {
  const result = foo();
  console.log(result);
}

function run(fn) {
  const oldFoo = foo;
  let cache = {
    status: 'pendding',
    data: null
  }
  foo = function() {
    if (cache.status === 'pendding') return cache
    throw oldFoo().then(res => {
      cache = {
        status: 'done',
        data: res
      }
    }).catch(err => {
      cache = {
        status: 'error',
        data: err
      }
    })
  }
  try {
    fn()
  } catch (err) {
    err.finally(() => {
      fn()
    })
  }
}

上面的例子很简单,就是针对异步的 foo 做了重写处理,我们模拟正常的接口代码:

javascript

// 我们的 getData 函数是异步的由头,接下来需要针对这个函数做异步传染处理
function getData(time = 1000) {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ name: "666", count: 0 }), time);
  });
}

function p1() {
  const res = getData(1500);
  return { ...res, count: res.count + 1 };
}

function p2() {
  const res = p1();
  return { ...res, count: res.count + 1 };
}

// 通过 run 函数对 getData 函数做异步传染处理
function run(fn) {
  const oldGetData = getData;
  let cache = {
    status: "pendding",
    data: null,
  };
  // 重写 getData
  getData = function () {
    if (cache.status !== "pendding") return cache.data;
    // 调用原来的 getData 函数
    const p = oldGetData().then(
      (res) => {
        cache = {
          status: "success",
          data: res,
        };
      },
      (err) => {
        cache = {
          status: "fail",
          data: err,
        };
      }
    );
    // 同步抛出错误,此时 promise 肯定还在 pendding 状态
    throw p;
  };
  try {
    // 捕获我们上面抛出的错误,拿到 promise 对象
    fn();
  } catch (err) {
    console.log(err);
    err.finally(() => {
      // 这里 promise 已经完成,我们重新执行一次 fn 函数即可
      fn();
      getData = oldGetData;
    });
  }
}

run(() => {
  const data = p2();
  console.log(data);
});

总结

异步传染可以用抛出错误的方式处理,但是存在一个非常致命的问题是整个函数链路执行了两次,这会导致性能问题,所以在性能和写法做取舍是我们需要考虑的。