有没有办法短路异步/等待流? [英] Is there a way to short circuit async/await flow?

查看:22
本文介绍了有没有办法短路异步/等待流?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

update 中下面调用的所有四个函数都返回承诺.

All four functions called below in update return promises.

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

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

What if we want to abort the sequence from outside, at any given time?

例如,当 fetchMetaData 正在执行时,我们意识到我们不再需要渲染组件,我们想取消剩余的操作(fetchContent渲染).有没有办法从 update 函数之外中止/取消这些操作?

For example, while fetchMetaData is being executed, we realize we no longer need to render the component and we want to cancel the remaining operations (fetchContent and render). Is there a way to abort/cancel these operations from outside the update function?

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

We could check against a condition after each await, but that seems like an inelegant solution, and even then we will have to wait for the current operation to finish.

推荐答案

我刚刚谈到了这个 - 这是一个可爱的话题,但遗憾的是你不会真的喜欢我将要提出的解决方案,因为它们是网关解决方案.

I just gave a talk about this - this is a lovely topic but sadly you're not really going to like the solutions I'm going to propose as they're gateway-solutions.

取消恰到好处"其实很辛苦.人们已经为此工作了一段时间,并决定不阻止异步功能.

Getting cancellation "just right" is actually very hard. People have been working on just that for a while and it was decided not to block async functions on it.

有两个提议试图在 ECMAScript 核心中解决这个问题:

There are two proposals attempting to solve this in ECMAScript core:

  • 取消令牌 - 添加旨在解决此问题的取消令牌.莉>
  • 可取消承诺 - 增加了 catch cancel (e) { 语法和 throw.cancel 语法旨在解决这个问题.
  • Cancellation tokens - which adds cancellation tokens that aim to solve this issue.
  • Cancelable promise - which adds catch cancel (e) { syntax and throw.cancel syntax which aims to address this issue.

这两个提案在上周都发生了重大变化,所以我不指望它们会在明年左右到达.这些提议有些互补,没有矛盾.

Both proposals changed substantially over the last week so I wouldn't count on either to arrive in the next year or so. The proposals are somewhat complimentary and are not at odds.

取消令牌很容易实现.可悲的是,您真正想要的那种取消(又名第三种状态取消,其中取消不是例外)目前对于异步函数是不可能的,因为您无法控制它们的运行方式.你可以做两件事:

Cancellation tokens are easy to implement. Sadly the sort of cancellation you'd really want (aka "third state cancellation where cancellation is not an exception) is impossible with async functions at the moment since you don't control how they're run. You can do two things:

  • 改用协程 - bluebird 附带了使用生成器和 promise 的声音消除功能,您可以使用它们.
  • 使用中止语义实现标记 - 这实际上很容易,所以让我们在这里做
  • Use coroutines instead - bluebird ships with sound cancellation using generators and promises which you can use.
  • Implement tokens with abortive semantics - this is actually pretty easy so let's do it here

好吧,令牌表示取消:

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); }
}

这会让你做类似的事情:

This would let you do something like:

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

这是一种非常丑陋的工作方式,最理想的是您希望异步函数意识到这一点,但它们(还没有).

Which is a really ugly way that would work, optimally you'd want async functions to be aware of this but they're not (yet).

最理想的情况是,您的所有临时函数都会知道并且会在取消时throw(同样,只是因为我们不能有第三种状态),如下所示:

Optimally, all your interim functions would be aware and would throw on cancellation (again, only because we can't have third-state) which would look like:

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 可以中止底层请求并抛出和以此类推.

Since each of our functions are cancellation aware, they can perform actual logical cancellation - getCdnUrls can abort the request and throw, fetchMetaData can abort the underlying request and throw and so on.

以下是在浏览器中使用 XMLHttpRequest API 编写 getCdnUrl(注意单数)的方法:

Here is how one might write getCdnUrl (note the singular) using the XMLHttpRequest API in browsers:

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;
}

这与没有协程的异步函数最接近.它不是很漂亮,但肯定可以使用.

This is as close as we can get with async functions without coroutines. It's not very pretty but it's certainly usable.

请注意,您希望避免将取消视为异常.这意味着,如果您的函数 throw 在取消时您需要在全局错误处理程序 process.on("unhandledRejection", e => ...诸如此类.

Note that you'd want to avoid cancellations being treated as exceptions. This means that if your functions throw on cancellation you need to filter those errors on the global error handlers process.on("unhandledRejection", e => ... and such.

这篇关于有没有办法短路异步/等待流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆