ES6 Promisesを使用してすべてのネットワークデータ取得を管理しますが、それらを強制的にキャンセルする必要がある状況がいくつかあります。
基本的にシナリオは、リクエストがバックエンドに委任されるUIで先行入力検索を行い、部分入力に基づいて検索を実行する必要があるようなものです。このネットワークリクエスト(#1)には少し時間がかかる場合がありますが、ユーザーは入力を続け、最終的に別のバックエンドコールをトリガーします(#2)
ここでは、#2が自然に#1より優先されるため、Promiseラッピングリクエスト#1をキャンセルしたいと思います。私はすでにデータ層にすべてのPromiseのキャッシュを持っているので、#2のPromiseを送信しようとしているときに理論的にそれを取得できます。
しかし、キャッシュから取得したPromise#1をキャンセルするにはどうすればよいですか?
誰かがアプローチを提案できますか?
ES6 promiseはキャンセルをサポートしていませんyet。それは進行中であり、そのデザインは多くの人々が本当に一生懸命取り組んだものです。 Soundキャンセルのセマンティクスを正しく理解するのは難しく、これは進行中の作業です。 「fetch」レポ、esdiscuss、およびGHの他のいくつかのレポジトリについては興味深い議論がありますが、私があなただったら辛抱強く待っています。
問題は、キャンセルが本当にクライアント側プログラミングの重要なシナリオであるという現実です。 Webリクエストの中止など、あなたが説明するケースは重要であり、どこにでもあります。
ええ、ごめんなさい。約束は、さらに物事が指定される前に最初に入らなければなりませんでした-したがって、.finally
および.cancel
-しかし、DOMを介して仕様に向かっています。キャンセルはnotであり、単なる時間の制約であり、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のようなライブラリは観察可能なライブラリであるため、ここで「輝いている」ことはありません。仕様に拘束されないことでユーザーレベルの約束ライブラリと同じ利点を持っています。 ES2016でオブザーバブルがネイティブになるのを待ちます。それらはaretypeaheadにとっては気の利いたものです。
キャンセル可能な約束の標準的な提案は失敗しました。
約束は、それを実現する非同期アクションのコントロールサーフェスではありません。所有者と消費者を混同します。代わりに、非同期functionsを作成します。これは、渡されたトークンを介してキャンセルできます。
別の約束は素晴らしいトークンを作り、キャンセルをPromise.race
で簡単に実装できるようにします:
例:Promise.race
を使用して、前のチェーンの効果をキャンセルします。
let cancel = () => {};
input.oninput = function(ev) {
let term = ev.target.value;
console.log(`searching for "${term}"`);
cancel();
let p = new Promise(resolve => cancel = resolve);
Promise.race([p, getSearchResults(term)]).then(results => {
if (results) {
console.log(`results for "${term}"`,results);
}
});
}
function getSearchResults(term) {
return new Promise(resolve => {
let timeout = 100 + Math.floor(Math.random() * 1900);
setTimeout(() => resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
});
}
Search: <input id="input">
ここでは、undefined
の結果を挿入してテストすることにより、以前の検索を「キャンセル」していますが、代わりに"CancelledError"
で拒否することを簡単に想像できます。
もちろん、これは実際にはネットワーク検索をキャンセルしませんが、それはfetch
の制限です。 fetch
が引数としてキャンセルプロミスを受け取る場合、ネットワークアクティビティをキャンセルできます。
proposed このes-discussの「約束のパターンをキャンセルする」、まさにfetch
がこれを行うことを示唆しています。
私はMozilla JSリファレンスをチェックアウトし、これを見つけました:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
それをチェックしよう:
_var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// Both resolve, but p2 is faster
});
_
ここにp1とp2がPromise.race(...)
を引数として入れていますが、これは実際に新しい解決プロミスを作成しているので、これが必要です。
Promise Extensions for JavaScript(Prex) を使用することをお勧めします。その著者 Ron Buckton はTypeScriptの重要なエンジニアの1人であり、現在のTC39の ECMAScript Cancellation 提案の背後にいる人物でもあります。ライブラリは十分に文書化されており、Prexの一部が標準に準拠する可能性があります。
個人的なメモで、C#の重いバックグラウンドから来た場合、Prexは既存の マネージスレッドでのキャンセル フレームワーク、つまりCancellationTokenSource
で取られたアプローチに基づいてモデル化されているという事実がとても気に入っています/ CancellationToken
.NET API。私の経験では、これらはマネージアプリに堅牢なキャンセルロジックを実装するのに非常に便利でした。
Nodeで _prex.CancellationTokenSource
_ を使用したキャンセルの遅延の例を次に示します。
_const prex = require('prex');
async function delayWithCancellation(timeoutMs, token) {
// this can easily be done without async/await,
// but I believe this linear structure is more readable
let reg = null;
try {
await new Promise((resolve, reject) => {
const id = setTimeout(resolve, timeoutMs);
reg = token.register(() => {
clearTimeout(id);
reject(new prex.CancelError("delay cancelled."));
});
});
}
finally {
reg && reg.unregister();
}
}
async function main() {
const tokenSource = new prex.CancellationTokenSource();
setTimeout(() => tokenSource.cancel(), 1500); // cancel after 1500ms
// without cancellation
await delayWithCancellation(1000, prex.CancellationToken.none);
console.log("successfully delayed once.");
// with cancellation
const token = tokenSource.token;
await delayWithCancellation(1500, token);
token.throwIfCancellationRequested();
console.log("successfully delayed twice."); // we should not be here
}
main().catch(e => console.log(e));
_
キャンセルはレースであることに注意してください。つまり、約束は正常に解決された可能性がありますが、(await
またはthen
を使用して)確認するまでに、キャンセルもトリガーされた可能性があります。このレースをどのように処理するかはあなた次第ですが、上記のようにtoken.throwIfCancellationRequested()
を余分に呼び出すことは決して痛いことはありません。
Updated、標準ネイティブ Promiseクラス をキャンセルサポート付きで拡張しました。これは Bluebirdで実装 (つまり、オプションのoncancel
コールバックを使用)、ただし_prex.CancellationTokenSource
_を使用します。 CancellablePromise
のコードは利用可能です ここ 。
最近、同様の問題に直面しました。
約束ベースのクライアント(ネットワークのクライアントではない)があり、UIをスムーズに保つために、常に最新の要求データをユーザーに提供したかったのです。
キャンセルのアイデアに苦労した後、Promise.race(...)
とPromise.all(..)
私はちょうど最後のリクエストIDを思い出し始め、promiseが満たされたとき、最後のリクエストのIDと一致したときにのみデータをレンダリングしていました。
それが誰かを助けることを願っています。
https://www.npmjs.com/package/promise-abortable を参照してください
$ npm install promise-abortable