有没有办法短路异步/等待流程?

2024-01-12

所有四个函数均在下面调用update回报承诺。

async function update() {
   var urls = await getCdnUrls();
   var metadata = await fetchMetaData(urls);
   var content = await fetchContent(metadata);
   await render(content);
   return;
}

如果我们想在任何给定时间从外部中止序列怎么办?

例如,虽然fetchMetaData正在执行时,我们意识到我们不再需要渲染组件,并且我们想要取消剩余的操作(fetchContent and render)。有没有办法从外部中止/取消这些操作update功能?

我们可以在每个之后检查一个条件await,但这似乎是一个不优雅的解决方案,即使如此,我们也必须等待当前操作完成。


现在执行此操作的标准方法是通过 AbortSignals

async function update({ signal } = {}) {
   // pass these to methods to cancel them internally in turn
   // this is implemented throughout Node.js and most of the web platform
   try {
     var urls = await getCdnUrls({ signal });
     var metadata = await fetchMetaData(urls);
     var content = await fetchContent(metadata);
     await render(content);
   } catch (e) {
      if(e.name !== 'AbortError') throw e;
   }
   return;
}
// usage
const ac = new AbortController();
update({ signal: ac.signal });
ac.abort(); // cancel the update

以下是 2016 年旧内容,小心龙

我刚刚对此进行了一次演讲 - 这是一个可爱的话题,但遗憾的是您不会真正喜欢我将提出的解决方案,因为它们是网关解决方案。

该规范对您有什么作用

“恰到好处”地取消取消实际上非常困难。人们已经为此工作了一段时间,并决定不阻止其上的异步函数。

ECMAScript 核心有两个提案试图解决这个问题:

  • 取消标记 https://github.com/zenparsing/es-cancel-token- 添加了旨在解决此问题的取消令牌。
  • 可取消的承诺 https://github.com/domenic/cancelable-promise- 这增加了catch cancel (e) { 语法和throw.cancel旨在解决这个问题的语法。

两项提案均发生了重大变化过去一周所以我不指望他们能在明年左右到达。这些建议有些互补,并不矛盾。

您可以采取什么措施来解决这个问题

取消令牌很容易实现。遗憾的是你会取消这种取消really想要(又名“第三状态 https://github.com/tc39/proposal-cancelable-promises/blob/2f601ac/Third%20State.md目前,异步函数不可能实现取消(取消也不例外),因为您无法控制它们的运行方式。你可以做两件事:

  • 使用协程代替 -bluebird http://bluebirdjs.com附带使用发电机消除声音的功能,并承诺您可以使用。
  • 使用中止语义实现标记 - 这实际上非常简单,所以让我们在这里完成

取消令牌

好吧,令牌表示取消:

class Token {
   constructor(fn) {
      this.isCancellationRequested = false; 
      this.onCancelled = []; // actions to execute when cancelled
      this.onCancelled.push(() => this.isCancellationRequested = true);
      // expose a promise to the outside
      this.promise = new Promise(resolve => this.onCancelled.push(resolve));
      // let the user add handlers
      fn(f => this.onCancelled.push(f));
   }
   cancel() { this.onCancelled.forEach(x => x); }
}

这会让你做类似的事情:

async function update(token) {
   if(token.isCancellationRequested) return;
   var urls = await getCdnUrls();
   if(token.isCancellationRequested) return;
   var metadata = await fetchMetaData(urls);
   if(token.isCancellationRequested) return;
   var content = await fetchContent(metadata);
   if(token.isCancellationRequested) return;
   await render(content);
   return;
}

var token = new Token(); // don't ned any special handling here
update(token);
// ...
if(updateNotNeeded) token.cancel(); // will abort asynchronous actions

这是一种非常丑陋的工作方式,最好你希望异步函数意识到这一点,但他们没有(yet).

最理想的情况是,所有临时职能部门都会意识到这一点并会throw取消时(同样,只是因为我们不能有第三状态),如下所示:

async function update(token) {
   var urls = await getCdnUrls(token);
   var metadata = await fetchMetaData(urls, token);
   var content = await fetchContent(metadata, token);
   await render(content, token);
   return;
}

由于我们的每个函数都是取消感知的,因此它们可以执行实际的逻辑取消 -getCdnUrls可以中止请求并抛出,fetchMetaData可以中止底层请求并抛出等。

这是一个人可能会写的方式getCdnUrl(注意单数)使用XMLHttpRequest浏览器中的API:

function getCdnUrl(url, token) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    var p = new Promise((resolve, reject) => {
      xhr.onload = () => resolve(xhr);
      xhr.onerror = e => reject(new Error(e));
      token.promise.then(x => { 
        try { xhr.abort(); } catch(e) {}; // ignore abort errors
        reject(new Error("cancelled"));
      });
   });
   xhr.send();
   return p;
}

这是我们在没有协程的情况下使用异步函数所能得到的最接近的结果。它不是很漂亮,但肯定有用。

请注意,您希望避免取消被视为异常。这意味着如果你的函数throw取消时,您需要在全局错误处理程序中过滤这些错误process.on("unhandledRejection", e => ...等等。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

有没有办法短路异步/等待流程? 的相关文章