1. ホーム
  2. javascript

[解決済み] vanilla ECMAScript 6 の Promise チェーンをキャンセルする

2022-06-15 13:08:13

質問

をクリアする方法はありますか? .then をクリアする方法はありますか? Promise のインスタンスですか?

の上にJavaScriptのテストフレームワークを書きました。 QUnit . このフレームワークでは、テストを同期的に実行するために、それぞれのテストを Promise . (このコードブロックの長さは申し訳ありません。 できる限りコメントしたので、面倒に感じないと思います)。

/* Promise extension -- used for easily making an async step with a
       timeout without the Promise knowing anything about the function 
       it's waiting on */
$$.extend(Promise, {
    asyncTimeout: function (timeToLive, errorMessage) {
        var error = new Error(errorMessage || "Operation timed out.");
        var res, // resolve()
            rej, // reject()
            t,   // timeout instance
            rst, // reset timeout function
            p,   // the promise instance
            at;  // the returned asyncTimeout instance

        function createTimeout(reject, tempTtl) {
            return setTimeout(function () {
                // triggers a timeout event on the asyncTimeout object so that,
                // if we want, we can do stuff outside of a .catch() block
                // (may not be needed?)
                $$(at).trigger("timeout");

                reject(error);
            }, tempTtl || timeToLive);
        }

        p = new Promise(function (resolve, reject) {
            if (timeToLive != -1) {
                t = createTimeout(reject);

                // reset function -- allows a one-time timeout different
                //    from the one original specified
                rst = function (tempTtl) {
                    clearTimeout(t);
                    t = createTimeout(reject, tempTtl);
                }
            } else {
                // timeToLive = -1 -- allow this promise to run indefinitely
                // used while debugging
                t = 0;
                rst = function () { return; };
            }

            res = function () {
                clearTimeout(t);
                resolve();
            };

            rej = reject;
        });

        return at = {
            promise: p,
            resolve: res,
            reject: rej,
            reset: rst,
            timeout: t
        };
    }
});

/* framework module members... */

test: function (name, fn, options) {
    var mod = this; // local reference to framework module since promises
                    // run code under the window object

    var defaultOptions = {
        // default max running time is 5 seconds
        timeout: 5000
    }

    options = $$.extend({}, defaultOptions, options);

    // remove timeout when debugging is enabled
    options.timeout = mod.debugging ? -1 : options.timeout;

    // call to QUnit.test()
    test(name, function (assert) {
        // tell QUnit this is an async test so it doesn't run other tests
        // until done() is called
        var done = assert.async();
        return new Promise(function (resolve, reject) {
            console.log("Beginning: " + name);

            var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
            $$(at).one("timeout", function () {
                // assert.fail() is just an extension I made that literally calls
                // assert.ok(false, msg);
                assert.fail("Test timed out");
            });

            // run test function
            var result = fn.call(mod, assert, at.reset);

            // if the test returns a Promise, resolve it before resolving the test promise
            if (result && result.constructor === Promise) {
                // catch unhandled errors thrown by the test so future tests will run
                result.catch(function (error) {
                    var msg = "Unhandled error occurred."
                    if (error) {
                        msg = error.message + "\n" + error.stack;
                    }

                    assert.fail(msg);
                }).then(function () {
                    // resolve the timeout Promise
                    at.resolve();
                    resolve();
                });
            } else {
                // if test does not return a Promise, simply clear the timeout
                // and resolve our test Promise
                at.resolve();
                resolve();
            }
        }).then(function () {
            // tell QUnit that the test is over so that it can clean up and start the next test
            done();
            console.log("Ending: " + name);
        });
    });
}

テストがタイムアウトした場合、私のタイムアウトPromiseは assert.fail() を実行し、テストは失敗とマークされます。それはそれで良いのですが、テストのプロミス ( result ) がまだ解決するのを待っているからです。

私はテストをキャンセルする良い方法が必要です。 私はフレームワークモジュールにフィールドを作成することによってそれを行うことができます this.cancelTest フィールドを作成し、頻繁にチェックします (たとえば、それぞれの then() の繰り返しの最初) にチェックします。 しかし、理想を言えば、私は $$(at).on("timeout", /* something here */) をクリアして、残りの then() をクリアする必要があります。 result 変数に追加することで、残りのテストが実行されないようにします。

このようなものは存在するのでしょうか?

クイックアップデート

を使ってみました。 Promise.race([result, at.promise]) . うまくいきませんでした。

アップデート2+混乱

ブロックを解除するために、数行で mod.cancelTest /pollingをテストアイデア内に追加しました。 (イベントトリガーも削除しました)。

return new Promise(function (resolve, reject) {
    console.log("Beginning: " + name);

    var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
    at.promise.catch(function () {
        // end the test if it times out
        mod.cancelTest = true;
        assert.fail("Test timed out");
        resolve();
    });

    // ...
    
}).then(function () {
    // tell QUnit that the test is over so that it can clean up and start the next test
    done();
    console.log("Ending: " + name);
});

にブレークポイントを設定しました。 catch ステートメントにブレークポイントを設定し、それがヒットしています。 今、私を混乱させているのは then() ステートメントが呼び出されないことです。 アイデアはありますか?

アップデート 3

最後の1つを把握した。 fn.call() は私がキャッチしていないエラーを投げていたので、テストプロミスは at.promise.catch() がそれを解決する前に拒否していました。

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

<ブロッククオート

をクリアする方法はありますか? .then をクリアする方法はありますか?

いいえ、少なくとも ECMAScript 6 ではそうではありません。プロミス(とその then ハンドラ) はデフォルトでキャンセル不可です。 (残念ながら) . es-discuss で少し議論されています (例. ここで など)で、正しい方法でこれを行う方法について少し議論されていますが、どのようなアプローチであれ、ES6に着地することはないでしょう。

現在の立場は、サブクラス化によって、独自の実装を使用してキャンセル可能な約束事を作成できるようになることです。 (それがどの程度うまくいくかはわからないが) .

言語委員会が最適な方法を見出すまで (ES7を希望?) が見つかるまでは、ユーザランドの Promise 実装を使うことができます。

現在の議論は https://github.com/domenic/cancelable-promise https://github.com/bergus/promise-cancellation の下書き。