现在执行此操作的标准方法是通过 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 => ...
等等。