약속-약속을 강제로 취소 할 수 있습니까?
ES6 Promises를 사용하여 모든 네트워크 데이터 검색을 관리하고 강제 취소해야하는 상황이 있습니다.
기본적으로 시나리오는 요청이 백엔드에 위임되는 UI에서 자동 완성 검색을 수행하여 부분 입력을 기반으로 검색을 수행해야합니다. 이 네트워크 요청 (# 1)은 약간의 시간이 걸릴 수 있지만 사용자는 계속 입력하여 결국 다른 백엔드 호출 (# 2)을 트리거합니다.
여기서 # 2는 당연히 # 1보다 우선하므로 Promise 래핑 요청 # 1을 취소하고 싶습니다. 이미 데이터 영역에 모든 약속의 캐시가 있으므로 # 2에 대한 약속을 제출하려고 할 때 이론적으로 검색 할 수 있습니다.
하지만 캐시에서 Promise # 1을 검색하면 어떻게 취소합니까?
누구든지 접근 방식을 제안 할 수 있습니까?
아니요. 아직 할 수 없습니다.
ES6 약속은 아직 취소를 지원하지 않습니다 . 진행 중이며 디자인은 많은 사람들이 정말 열심히 노력한 것입니다. 사운드 취소 의미론은 제대로 이해하기 어렵고 진행중인 작업입니다. "가져 오기"저장소, esdiscuss 및 GH의 다른 여러 저장소에 대한 흥미로운 논쟁이 있지만 내가 당신이라면 인내심을 가질 것입니다.
근데 근데 .. 취소가 정말 중요 해요!
문제의 현실은 취소가 클라이언트 측 프로그래밍에서 정말 중요한 시나리오라는 것입니다. 웹 요청 중단과 같이 설명하는 경우는 중요하며 어디에나 있습니다.
그래서 ... 언어가 나를 망 쳤어!
네, 죄송합니다. Promise는 더 많은 것들이 지정되기 전에 먼저 들어가야했습니다. 그래서 그들은 .finally
그리고 같은 유용한 것없이 들어갔습니다. .cancel
하지만 DOM을 통해 사양으로가는 중입니다. 취소는 나중에 생각할 필요 가 없으며 시간 제약 일 뿐이며 API 설계에 대한보다 반복적 인 접근 방식입니다.
그래서 내가 무엇을 할 수 있니?
몇 가지 대안이 있습니다.
- 사양보다 훨씬 빠르게 이동할 수있는 블루 버드 와 같은 타사 라이브러리를 사용하여 취소 및 기타 혜택을받을 수 있습니다. 이것이 WhatsApp과 같은 대기업이하는 일입니다.
- 취소 토큰을 전달 합니다.
타사 라이브러리를 사용하는 것은 매우 분명합니다. 토큰의 경우 다음과 같이 메서드가 함수를 가져온 다음 호출하도록 할 수 있습니다.
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이 네이티브가 될 때 ES2016에서 볼 수 있기를 기다릴 것입니다. 그들은 이다 그러나 선행 입력을위한 멋진.
취소 가능한 약속에 대한 표준 제안이 실패했습니다.
promise는이를 수행하는 비동기 작업의 제어 표면이 아닙니다. 소유자와 소비자를 혼동합니다. 대신 전달 된 토큰을 통해 취소 할 수있는 비동기 함수 를 만듭니다 .
또 다른 약속은 훌륭한 토큰을 만들어 취소를 쉽게 구현할 수 있도록합니다 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
A는 인수로 약속을 취소 걸릴했다, 다음은 네트워크 활동을 취소 할 수 있습니다.
내가 한 제안 이의 "약속 패턴 취소"정확히 제안, ES를-토론 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(...)
를 인수로 넣었습니다 . 이것은 실제로 여러분이 필요로하는 새로운 해결 약속을 생성하는 것입니다.
Node.js 및 Electron의 경우 Promise Extensions for JavaScript (Prex)를 사용하는 것이 좋습니다 . 저자 인 Ron Buckton 은 주요 TypeScript 엔지니어 중 한 명이며 현재 TC39의 ECMAScript Cancellation 제안 의 배후에있는 사람이기도합니다 . 라이브러리는 잘 문서화되어 있으며 Prex의 일부가 표준에 적용될 가능성이 있습니다.
개인적인 메모와 무거운 C # 배경에서 나는 Prex가 Managed Threads 프레임 워크 의 기존 취소 , 즉 CancellationTokenSource
/ CancellationToken
.NET API로 취한 접근 방식을 기반으로 모델링되었다는 사실을 매우 좋아 합니다. 내 경험상 관리되는 앱에서 강력한 취소 논리를 구현하는 데 매우 편리했습니다.
또한 Browserify를 사용하여 Prex를 번들링하여 브라우저 내에서 작동하는지 확인했습니다 .
다음은 취소로 인한 지연의 예입니다 prex.CancellationTokenSource
.
// https://stackoverflow.com/a/53093799
const prex = require('prex');
// delayWithCancellation
function delayWithCancellation(timeoutMs, token) {
console.log(`delayWithCancellation: ${timeoutMs}`);
return createCancellablePromise((resolve, reject, setCancelListener) => {
token.throwIfCancellationRequested();
const id = setTimeout(resolve, timeoutMs);
setCancelListener(e => clearTimeout(id));
}, token);
}
// main
async function main() {
const tokenSource = new prex.CancellationTokenSource();
setTimeout(() => tokenSource.cancel(), 2000); // cancel after 1500ms
const token = tokenSource.token;
await delayWithCancellation(1000, token);
console.log("successfully delayed."); // we should reach here
await delayWithCancellation(1500, token);
console.log("successfully delayed."); // we should not reach here
}
// createCancellablePromise - create a Promise that gets rejected
// when cancellation is requested on the token source
async function createCancellablePromise(executor, token) {
if (!token) {
return await new Promise(executor);
}
// prex.Deferred is similar to TaskCompletionsource in .NET
const d = new prex.Deferred();
// function executor(resolve, reject, setCancelListener)
// function oncancel(CancelError)
executor(d.resolve, d.reject, oncancel =>
d.oncancel = oncancel);
const reg = token.register(() => {
// the token cancellation callback is synchronous,
// and so is the d.oncancel callback
try {
// capture the CancelError
token.throwIfCancellationRequested();
}
catch (e) {
try {
d.oncancel && d.oncancel(e);
// reject here if d.oncancel did not resolve/reject
d.reject(e);
}
catch (e2) {
d.reject(e2);
}
}
});
try {
await d.promise;
}
finally {
reg.unregister();
}
}
main().catch(e => console.log(e));
Note that cancellation is a race. I.e., a promise may have been resolved successfully, but by the time you observe it (with await
or then
), the cancellation may have been triggered as well. It's up to you how you handle this race, but it doesn't hurts to call token.throwIfCancellationRequested()
an extra time, like I do above.
I faced similar problem recently.
I had a promise based client (not a network one) and i wanted to always give the latest requested data to the user to keep the UI smooth.
After struggling with cancellation idea, Promise.race(...)
and Promise.all(..)
i just started remembering my last request id and when promise was fulfilled i was only rendering my data when it matched the id of a last request.
Hope it helps someone.
See https://www.npmjs.com/package/promise-abortable
$ npm install promise-abortable
Because @jib reject my modify, so I post my answer here. It's just the modfify of @jib's anwser with some comments and using more understandable variable names.
Below I just show examples of two different method: one is resolve() the other is reject()
let cancelCallback = () => {};
input.oninput = function(ev) {
let term = ev.target.value;
console.log(`searching for "${term}"`);
cancelCallback(); //cancel previous promise by calling cancelCallback()
let setCancelCallbackPromise = () => {
return new Promise((resolve, reject) => {
// set cancelCallback when running this promise
cancelCallback = () => {
// pass cancel messages by resolve()
return resolve('Canceled');
};
})
}
Promise.race([setCancelCallbackPromise(), getSearchResults(term)]).then(results => {
// check if the calling of resolve() is from cancelCallback() or getSearchResults()
if (results == 'Canceled') {
console.log("error(by resolve): ", results);
} else {
console.log(`results for "${term}"`, results);
}
});
}
input2.oninput = function(ev) {
let term = ev.target.value;
console.log(`searching for "${term}"`);
cancelCallback(); //cancel previous promise by calling cancelCallback()
let setCancelCallbackPromise = () => {
return new Promise((resolve, reject) => {
// set cancelCallback when running this promise
cancelCallback = () => {
// pass cancel messages by reject()
return reject('Canceled');
};
})
}
Promise.race([setCancelCallbackPromise(), getSearchResults(term)]).then(results => {
// check if the calling of resolve() is from cancelCallback() or getSearchResults()
if (results !== 'Canceled') {
console.log(`results for "${term}"`, results);
}
}).catch(error => {
console.log("error(by reject): ", error);
})
}
function getSearchResults(term) {
return new Promise(resolve => {
let timeout = 100 + Math.floor(Math.random() * 1900);
setTimeout(() => resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
});
}
Search(use resolve): <input id="input">
<br> Search2(use reject and catch error): <input id="input2">
참고URL : https://stackoverflow.com/questions/30233302/promise-is-it-possible-to-force-cancel-a-promise
'Development Tip' 카테고리의 다른 글
C에서 char 배열을 복사하는 방법은 무엇입니까? (0) | 2020.11.10 |
---|---|
Lambda : 로컬 변수에는 최종, 인스턴스 변수는 필요하지 않습니다. (0) | 2020.11.10 |
전자와 함께 sqlite3 모듈을 사용하는 방법은 무엇입니까? (0) | 2020.11.10 |
SSL이 최대 허용 길이를 초과하는 레코드를 받았습니다. (0) | 2020.11.10 |
bash에서 fork 및 exec (0) | 2020.11.10 |