async/await フローを短絡する方法はありますか? 質問する

async/await フローを短絡する方法はありますか? 質問する

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 つあります。

両方の提案は大幅に変更された先週したがって、私は、どちらも今後 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 => ...などでそれらのエラーをフィルター処理する必要があります。

おすすめ記事