1. ホーム
  2. javascript

[解決済み] Promise - 約束を強制的にキャンセルすることは可能か?

2022-08-20 19:02:56

質問

ES6プロミスを使用して、ネットワークデータの取得を管理していますが、強制的にキャンセルする必要がある場合があります。

基本的にシナリオは、私がUI上でタイプアヘッド検索を行い、リクエストがバックエンドに委ねられ、部分的な入力に基づいて検索を実行するようなものです。このネットワーク リクエスト (#1) には少し時間がかかるかもしれませんが、ユーザーは入力を続け、最終的に別のバックエンド コール (#2) をトリガーします。

ここで#2は当然#1より優先されるので、Promiseのラッピングリクエスト#1をキャンセルしたいと思います。私はすでにデータレイヤーにすべてのPromiseのキャッシュを持っているので、理論的には#2のPromiseを送信しようとしているときにそれを取り出すことができます。

しかし、一旦キャッシュから取り出したら、どのようにプロミス#1をキャンセルするのでしょうか?

どなたかアプローチを提案していただけませんか?

どのように解決するのですか?

モダンなJavaScriptでは - いいえ

プロミスが定着し(hah)、(保留中の)プロミスをキャンセルすることは不可能になったようです。

代わりに、WHATWG (HTML を構築する標準化団体) の一部として、クロスプラットフォーム (Node, Browsers など) のキャンセルプリミティブである AbortController . これを使うと 機能 をキャンセルするために使うことができます。

// Take a signal parameter in the function that needs cancellation
async function somethingIWantToCancel({ signal } = {}) {
  // either pass it directly to APIs that support it
  // (fetch and most Node APIs do)
  const response = await fetch('.../', { signal });
  // return response.json;

  // or if the API does not already support it -
  // manually adapt your code to support signals:
  const onAbort = (e) => {
    // run any code relating to aborting here
  };
  signal.addEventListener('abort', onAbort, { once: true });
  // and be sure to clean it up when the action you are performing
  // is finished to avoid a leak
  // ... sometime later ...
  signal.removeEventListener('abort', onAbort);
}

// Usage
const ac = new AbortController();
setTimeout(() => ac.abort(), 1000); // give it a 1s timeout
try {
  await somethingIWantToCancel({ signal: ac.signal });
} catch (e) {
  if (e.name === 'AbortError') {
    // deal with cancellation in caller, or ignore
  } else {
    throw e; // don't swallow errors :)
  }
}


いいえ、まだできません。

ES6 のプロミスはキャンセルをサポートしていません まだ . その設計は、多くの人が一生懸命に取り組んだものです。 <次ページ 音 キャンセル セマンティクスを正しく理解することは難しく、これは進行中の作業です。GH の "fetch" レポ、esdiscuss、および他のいくつかのレポで興味深い議論が行われていますが、私があなただったら、ただ辛抱強く待つと思います。

でも、でも、でも......キャンセルって本当に大事なんですよ!

そうなんです、現実問題としてキャンセルは 本当に であり、クライアント側プログラミングの重要なシナリオです。Web リクエストを中断するようなケースは重要であり、どこにでもあります。

だから...言語が私をダメにした!

ええ、その点については申し訳ありません。プロミスは他のものが指定される前に最初に入れなければならなかったので、次のような便利なものがないまま入ってしまいました。 .finally.cancel - は、DOMを経由して仕様に向かうのですが、その途中です。キャンセルは ではなく 時間的な制約と API デザインへのより反復的なアプローチにすぎません。

では、どうすればいいのでしょうか?

いくつかの選択肢があります。

  • のようなサードパーティライブラリを使用する。 ブルーバード のようなサードパーティライブラリを使用することで、仕様よりもずっと速く動くことができ、その結果、キャンセルや他の多くのグッズを持つことができます。
  • キャンセルを渡す トークン .

サードパーティライブラリを使うのは、ごく当たり前のことです。トークンについては、このように、メソッドに関数を取り込ませて、それを呼び出すようにすればよいでしょう。

function getWithCancel(url, token) { // the token is for cancellation
   var xhr = new XMLHttpRequest;
   xhr.open("GET", url);
   return new Promise(function(resolve, reject) {
      xhr.onload = function() { resolve(xhr.responseText); });
      token.cancel = function() {  // SPECIFY CANCELLATION
          xhr.abort(); // abort request
          reject(new Error("Cancelled")); // reject the promise
      };
      xhr.onerror = reject;
   });
};

ということができるようになる。

var token = {};
var promise = getWithCancel("/someUrl", token);

// later we want to abort the promise:
token.cancel();

実際の使用例 last

これはトークンのアプローチではそれほど難しいことではありません。

function last(fn) {
    var lastToken = { cancel: function(){} }; // start with no op
    return function() {
        lastToken.cancel();
        var args = Array.prototype.slice.call(arguments);
        args.push(lastToken);
        return fn.apply(this, args);
    };
}

ということができるようになる。

var synced = last(getWithCancel);
synced("/url1?q=a"); // this will get canceled 
synced("/url1?q=ab"); // this will get canceled too
synced("/url1?q=abc");  // this will get canceled too
synced("/url1?q=abcd").then(function() {
    // only this will run
});

そして、Bacon や Rx のようなライブラリは、observable ライブラリだからといって、ここで "shine" するのではなく、仕様に縛られないことでユーザーレベルのプロミスライブラリが持っているのと同じ利点を持っているだけです。ES2016でobservableがネイティブになるのを待つしかないでしょう。これらは しかし、typeaheadのためのニフティです。