바닐라 ECMAScript 6 Promise 체인 취소
.then
JavaScript 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()
되었습니다.
.then
JavaScript Promise 인스턴스 를 지우는 방법이 있습니까?
아니요. 적어도 ECMAScript 6에는 없습니다. 약속 (및 해당 then
처리기)은 기본적으로 취소 할 수 없습니다 (불행히도) . 이 작업을 올바른 방식으로 수행하는 방법에 대한 es-discuss (예 : 여기 )에 대한 약간의 논의가 있지만 어떤 접근 방식이 이길 지 ES6에는 적용되지 않습니다.
현재 관점은 서브 클래 싱이 자신의 구현을 사용하여 취소 할 수있는 약속을 만들 수 있다는 것입니다 (얼마나 잘 작동하는지 확실하지 않음) .
언어 커밋이 가장 좋은 방법을 찾을 때까지 (ES7 바라건대?) 여전히 userland Promise 구현을 사용할 수 있으며, 그중 많은 기능이 취소됩니다.
현재 토론은 https://github.com/domenic/cancelable-promise 및 https://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();
취소 할 수있는 약속을위한 몇 개의 npm 라이브러리가 있습니다.
p-cancelable https://github.com/sindresorhus/p-cancelable
간단한 버전 :
거부 기능을 제공하십시오.
function Sleep(ms,cancel_holder) {
return new Promise(function(resolve,reject){
var done=false;
var t=setTimeout(function(){if(done)return;done=true;resolve();}, ms);
cancel_holder.cancel=function(){if(done)return;done=true;if(t)clearTimeout(t);reject();}
})
}
포장기 솔루션 (공장)
내가 찾은 해결책은 cancel_holder 객체를 전달하는 것입니다. 취소 기능이 있습니다. 취소 기능이 있으면 취소 할 수 있습니다.
이 취소 함수는 Error ( 'canceled')와 함께 약속을 거부합니다.
resolve, reject 또는 on_cancel 전에는 취소 함수가 이유없이 호출되는 것을 방지합니다.
취소 작업을 주입으로 전달하는 것이 편리하다는 것을 알았습니다.
function cancelablePromise(cancel_holder,promise_fn,optional_external_cancel) {
if(!cancel_holder)cancel_holder={};
return new Promise( function(resolve,reject) {
var canceled=false;
var resolve2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; resolve.apply(this,arguments);}
var reject2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; reject.apply(this,arguments);}
var on_cancel={}
cancel_holder.cancel=function(){
if(canceled) return; canceled=true;
delete cancel_holder.cancel;
cancel_holder.canceled=true;
if(on_cancel.cancel)on_cancel.cancel();
if(optional_external_cancel)optional_external_cancel();
reject(new Error('canceled'));
};
return promise_fn.call(this,resolve2,reject2,on_cancel);
});
}
function Sleep(ms,cancel_holder) {
return cancelablePromise(cancel_holder,function(resolve,reject,oncacnel){
var t=setTimeout(resolve, ms);
oncacnel.cancel=function(){if(t)clearTimeout(t);}
})
}
let cancel_holder={};
// meanwhile in another place it can be canceled
setTimeout(function(){ if(cancel_holder.cancel)cancel_holder.cancel(); },500)
Sleep(1000,cancel_holder).then(function() {
console.log('sleept well');
}, function(e) {
if(e.message!=='canceled') throw e;
console.log('sleep interrupted')
})
실제로 약속 실행을 중지하는 것은 불가능하지만 거부를 가로 채서 약속 자체에서 호출 할 수 있습니다.
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('Fucked up!'));
}, 1000);
모든 thens / catch가 실행되는 것을 중지하려면 결코 해결되지 않는 promise를 삽입하여이를 수행 할 수 있습니다. 아마도 메모리 누수 반복이있을 수 있지만 문제를 해결하고 대부분의 응용 프로그램에서 너무 많은 메모리를 낭비해서는 안됩니다.
new Promise((resolve, reject) => {
console.log('first chain link executed')
resolve('daniel');
}).then(name => {
console.log('second chain link executed')
if (name === 'daniel') {
// I don't want to continue the chain, return a new promise
// that never calls its resolve function
return new Promise((resolve, reject) => {
console.log('unresolved promise executed')
});
}
}).then(() => console.log('last chain link executed'))
// VM492:2 first chain link executed
// VM492:5 second chain link executed
// VM492:8 unresolved promise executed
다음은 우리의 구현입니다 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
전화 내에서 추가로 취소하겠습니다.- 다른 제안이나 구현과 달리 해결 되지 않고 거부 되는 취소에 의존
가져 오기 및 의견 환영
신호를 약속에 "취소"속성을 설정 then()
및 catch()
조기 종료 할 수 있습니다. 특히 onmessage
핸들러의 Promises에 기존 마이크로 태스크가 대기열에있는 Web Worker에서 매우 효과적 입니다.
// Queue task to resolve Promise after the end of this script
const promise = new Promise(resolve => setTimeout(resolve))
promise.then(_ => {
if (promise.canceled) {
log('Promise cancelled. Exiting early...');
return;
}
log('No cancelation signaled. Continue...');
})
promise.canceled = true;
function log(msg) {
document.body.innerHTML = msg;
}
@Michael Yagudaev의 답변이 저에게 효과적입니다.
그러나 원래 답변은 거부 처리를 처리하기 위해 래핑 된 약속을 .catch ()로 연결하지 않았습니다. 여기 @Michael Yagudaev의 답변 위에 개선 사항이 있습니다.
const makeCancelablePromise = promise => {
let hasCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise
.then(val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)))
.catch(
error => (hasCanceled ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled = true;
}
};
};
// Example Usage:
const cancelablePromise = makeCancelable(
new Promise((rs, rj) => {
/*do something*/
})
);
cancelablePromise.promise.then(() => console.log('resolved')).catch(err => {
if (err.isCanceled) {
console.log('Wrapped promise canceled');
return;
}
console.log('Promise was not canceled but rejected due to errors: ', err);
});
cancelablePromise.cancel();
p가 Promise를 포함하는 변수 인 p.then(empty);
경우, 결국 완료되거나 이미 완료되면 promise를 무시해야합니다 (예, 이것이 원래 질문이 아니라는 것을 알고 있지만 제 질문입니다). "비어 있음"은 function empty() {}
입니다. 나는 단지 초보자이고 아마도 틀렸을 것입니다. 그러나 다른 대답은 너무 복잡해 보입니다. 약속은 단순해야합니다.
promise-abortable 시도 : https://www.npmjs.com/package/promise-abortable
$ npm install promise-abortable
import AbortablePromise from "promise-abortable";
const timeout = new AbortablePromise((resolve, reject, signal) => {
setTimeout(reject, timeToLive, error);
signal.onabort = resolve;
});
Promise.resolve(fn()).then(() => {
timeout.abort();
});
참고 URL : https://stackoverflow.com/questions/29478751/cancel-a-vanilla-ecmascript-6-promise-chain
'code' 카테고리의 다른 글
정의되지 않은 것을 덮어 쓰지 않는다고 가정하는 것이 JavaScript에서 실제로 얼마나 위험한가요? (0) | 2020.09.22 |
---|---|
IIS 앱 풀, 작업자 프로세스, 앱 도메인 (0) | 2020.09.22 |
Eclipse 작업 공간을 복제하는 방법 (0) | 2020.09.22 |
익명 함수로 addEventListener 인 EventListener를 제거하는 방법은 무엇입니까? (0) | 2020.09.22 |
C # 출력 매개 변수에 해당하는 VB.NET이 있습니까? (0) | 2020.09.22 |