update
以下の 4 つの関数はすべて、return promiseで呼び出されます。
async function update() {
var urls = await getCdnUrls();
var metadata = await fetchMetaData(urls);
var content = await fetchContent(metadata);
await render(content);
return;
}
いつでも外部からシーケンスを中止したい場合はどうすればよいでしょうか?
たとえば、fetchMetaData
の実行中に、コンポーネントをレンダリングする必要がなくなったため、残りの操作 (fetchContent
およびrender
) をキャンセルしたいとします。関数の外部からこれらの操作を中止/キャンセルする方法はありますかupdate
?
それぞれの後に条件をチェックすることもできますawait
が、これはあまりスマートではない解決策のように思えますし、その場合でも現在の操作が完了するまで待たなければなりません。
ベストアンサー1
現在、これを実行する標準的な方法は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 コアでこの問題を解決しようとする提案が 2 つあります。
- キャンセルトークン- この問題を解決することを目的としたキャンセル トークンを追加します。
- キャンセル可能な約束-この問題に対処することを目的とした
catch cancel (e) {
構文と構文を追加します。throw.cancel
両方の提案は大幅に変更された先週したがって、私は、どちらも今後 1 年程度で実現するとは期待していません。これらの提案は、ある程度相補的であり、対立するものではありません。
これを解決するためにあなた側でできること
キャンセルトークンは簡単に実装できます。残念ながら、本当に欲しい(別名「第三の状態非同期関数でキャンセル (キャンセルが例外ではない場合) を実行することは、実行方法を制御できないため、現時点では不可能です。次の 2 つの方法があります。
- 代わりにコルーチンを使用してください -青い鳥ジェネレーターとプロミスを使用したサウンドキャンセル機能が搭載されており、使用できます。
- 中止セマンティクスを持つトークンを実装する - これは実際には非常に簡単なので、ここでやってみましょう
キャンセルトークン
さて、トークンはキャンセルを通知します。
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
キャンセルされることです (これも、第 3 の状態を持つことができないため)。これは次のようになります。
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 => ...
などでそれらのエラーをフィルター処理する必要があります。