现在执行此操作的标准方法是通过 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
OLD 2016 内容如下,小心龙
我刚刚讨论了这个——这是一个很好的话题,但遗憾的是你不会真正喜欢我要提出的解决方案,因为它们是网关解决方案。
规范为您做什么
取消“恰到好处”实际上非常困难。人们已经为此工作了一段时间,并决定不阻止异步功能。
在 ECMAScript 核心中有两个提议试图解决这个问题:
- 取消令牌- 添加旨在解决此问题的取消令牌。
- 可取消的承诺- 添加旨在解决此问题的
catch cancel (e) {
语法和语法。throw.cancel
这两项提案在上周发生了重大变化,因此我不会指望任何一个会在明年左右到达。这些建议有些互补,并不矛盾。
你可以做些什么来从你身边解决这个问题
取消令牌很容易实现。遗憾的是,由于您无法控制它们的运行方式,因此目前异步函数不可能实现您真正想要的那种取消(又名“第三状态取消,其中取消也不例外)。您可以做两件事:
- 改用协程 - bluebird使用您可以使用的生成器和承诺提供声音消除功能。
- 实现带有中止语义的标记 - 这实际上很容易,所以让我们在这里做
取消令牌
好吧,令牌表示取消:
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
这是一种非常丑陋的工作方式,最好是您希望异步函数意识到这一点,但它们还没有(还)。
最理想的情况是,您的所有临时函数都会知道并会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
可以中止底层请求并抛出等等。
以下是在浏览器中使用 API编写getCdnUrl
(注意单数)的方式:XMLHttpRequest
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 => ...
等上过滤这些错误。