[javascript] 바닐라 ECMAScript 6 Promise 체인 취소

.thenJavaScript Promise인스턴스 를 지우는 방법이 있습니까?

QUnit 위에 JavaScript 테스트 프레임 워크를 작성했습니다 . 프레임 워크는 .NET Framework에서 각각을 실행하여 테스트를 동 기적으로 실행합니다 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()테스트에서 테스트를 수행하여 테스트가 실패로 표시됩니다. 이는 모두 훌륭하고 양호하지만 테스트 Promise ( result)가 여전히 문제를 해결하기를 기다리고 있기 때문에 테스트가 계속 실행 됩니다.

시험을 취소 할 좋은 방법이 필요합니다. 프레임 워크 모듈 this.cancelTest이나 무언가 에 필드를 만들고 then()테스트 내에서 매번 (예 : 각 반복 시작시 ) 취소할지 여부를 확인하여 수행 할 수 있습니다. 그러나 이상적으로 는 나머지 테스트가 실행되지 않도록 내 변수 $$(at).on("timeout", /* something here */)의 나머지 then()s 를 지우는 데 사용할 수 있습니다 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

마지막 일을 알아 냈습니다. fn.call()내가 잡지 못한 오류를 던지고 있었기 때문에 테스트 약속은 전에 거부 at.promise.catch()되었습니다.



답변

.thenJavaScript Promise 인스턴스 를 지우는 방법이 있습니까?

아니요. 적어도 ECMAScript 6에는 없습니다. 약속 (및 해당 then처리기)은 기본적으로 취소 할 수 없습니다 (불행히도) . 이 작업을 올바른 방식으로 수행하는 방법에 대한 es-discuss (예 : 여기 )에 대한 약간의 논의가 있지만 어떤 접근 방식이 이길 지 ES6에는 적용되지 않습니다.

현재 관점은 서브 클래 싱이 자신의 구현을 사용하여 취소 할 수있는 약속을 만들 수 있다는 것입니다 (얼마나 잘 작동하는지 확실하지 않음) .

언어 커밋이 가장 좋은 방법을 찾을 때까지 (ES7 바라건대?) 여전히 userland Promise 구현을 사용할 수 있으며, 그중 많은 기능이 취소됩니다.

현재 토론은 https://github.com/domenic/cancelable-promisehttps://github.com/bergus/promise-cancellation 초안에 있습니다.


답변

ES6에서이를 수행하는 표준 방법은 없지만이 를 처리하기위한 Bluebird 라는 라이브러리 가 있습니다.

반응 문서의 일부로 설명 된 권장 방법도 있습니다. 2 차 및 3 차 업데이트에있는 것과 비슷해 보입니다.

const makeCancelable = (promise) => {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then((val) =>
      hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
    );
    promise.catch((error) =>
      hasCanceled_ ? reject({isCanceled: true}) : reject(error)
    );
  });

  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};

const cancelablePromise = makeCancelable(
  new Promise(r => component.setState({...}}))
);

cancelablePromise
  .promise
  .then(() => console.log('resolved'))
  .catch((reason) => console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise

출처 : https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html


답변

아무도 Promise.race이것에 대한 후보로 언급하지 않는다는 것에 정말 놀랐습니다 .

const actualPromise = new Promise((resolve, reject) => { setTimeout(resolve, 10000) });
let cancel;
const cancelPromise = new Promise((resolve, reject) => {
    cancel = reject.bind(null, { canceled: true })
})

const cancelablePromise = Object.assign(Promise.race([actualPromise, cancelPromise]), { cancel });


답변

const makeCancelable = promise => {
    let rejectFn;

    const wrappedPromise = new Promise((resolve, reject) => {
        rejectFn = reject;

        Promise.resolve(promise)
            .then(resolve)
            .catch(reject);
    });

    wrappedPromise.cancel = () => {
        rejectFn({ canceled: true });
    };

    return wrappedPromise;
};

용법:

const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();


답변

실제로 약속 실행을 중지하는 것은 불가능하지만 거부를 가로 채서 약속 자체에서 호출 할 수 있습니다.

class CancelablePromise {
  constructor(executor) {
    let _reject = null;
    const cancelablePromise = new Promise((resolve, reject) => {
      _reject = reject;
      return executor(resolve, reject);
    });
    cancelablePromise.cancel = _reject;

    return cancelablePromise;
  }
}

용법:

const p = new CancelablePromise((resolve, reject) => {
  setTimeout(() => {
    console.log('resolved!');
    resolve();
  }, 2000);
})

p.catch(console.log);

setTimeout(() => {
  p.cancel(new Error('Messed up!'));
}, 1000);


답변

취소 할 수있는 약속을위한 몇 개의 npm 라이브러리가 있습니다.

  1. p-cancelable
    https://github.com/sindresorhus/p-cancelable

  2. 취소 가능 약속
    https://github.com/alkemics/CancelablePromise


답변

다음은 우리의 구현입니다 https://github.com/permettez-moi-de-construire/cancellable-promise

같이 사용

const {
  cancellablePromise,
  CancelToken,
  CancelError
} = require('@permettezmoideconstruire/cancellable-promise')

const cancelToken = new CancelToken()

const initialPromise = SOMETHING_ASYNC()
const wrappedPromise = cancellablePromise(initialPromise, cancelToken)


// Somewhere, cancel the promise...
cancelToken.cancel()


//Then catch it
wrappedPromise
.then((res) => {
  //Actual, usual fulfill
})
.catch((err) => {
  if(err instanceOf CancelError) {
    //Handle cancel error
  }

  //Handle actual, usual error
})

어느 :

  • Promise API를 건드리지 않음
  • catch전화 내에서 추가로 취소하겠습니다.
  • 다른 제안이나 구현과 달리 해결 되지 않고 거부 되는 취소에 의존

가져 오기 및 의견 환영