![](/img/trans.png)
[英]Is there a practical way to use JavaScript/HTML as a proxy for acoustic echo cancellation in ActionScript 3.0?
[英]What is a practical / elegant way to manage complex event sequences with cancellation in JavaScript?
我有一個JavaScript( EmberJS + Electron )應用程序,需要執行異步任務序列。 這是一個簡化的示例:
t1
秒后收到回復 t2
秒后收到第二個響應 對於簡單的情況,使用Promises似乎很容易實現:1,然后2,然后3 ...當合並超時時,這會變得有些棘手,但是Promise.race
和Promise.all
似乎都是解決此問題的合理方法。
但是,我需要允許用戶能夠優雅地取消序列,並且我正在努力思考這樣做的明智方法。 首先想到的是在每個步驟中進行某種輪詢,以查看是否已在某個位置設置了變量以指示應取消該序列。 當然,這有一些嚴重的問題:
cancel
事件與時間完全無關,因此不需要使用計時器。 isCanceled
變量可能需要超出promise的范圍。 等等 我的另一個想法是,也許到目前為止,所有事情都與另一個僅在用戶發送取消信號時才解決的承諾相沖突。 這里的一個主要問題是,正在運行的單個任務(用戶要取消)不知道它們需要停止,回滾等,因此即使從競爭中獲得承諾解決方案的代碼運行正常,其他諾言中的代碼不會得到通知。
曾幾何時,人們談論 可取消的諾言 ,但在我看來,該提案已撤回,因此盡管我認為BlueBird諾言庫支持此想法,但也不會很快納入ECMAScript中。 我正在制作的應用程序已經包含RSVP Promise庫 ,因此我並不是真的想引入另一個庫 ,但是我想這是一個潛在的選擇。
還有什么可以解決這個問題的呢? 我應該完全使用諾言嗎? 通過發布/訂閱事件系統或諸如此類的事情可以更好地解決此問題?
理想情況下,我想將被取消的關注與每個任務分開(就像Promise
對象如何處理異步的關注一樣)。 如果取消信號可以是傳入/注入的信號,那也很好。
盡管沒有圖形技能,但我還是嘗試通過制作下面的兩張圖來說明我正在嘗試做的事情。 如果您發現它們令人困惑,請隨時忽略它們。
如果我正確理解您的問題,則可能是以下解決方案。
假設您的主線代碼如下所示:
send(msg1)
.then(() => receive(t1))
.then(() => send(msg2))
.then(() => receive(t2))
.catch(() => console.log("Didn't complete sequence"));
receive
將類似於:
function receive(t) {
return new Promise((resolve, reject) => {
setTimeout(() => reject("timed out"), t);
receiveMessage(resolve, reject);
});
}
假定存在底層API receiveMessage
,該API將兩個回調作為參數,一個用於成功,一個用於失敗。 receive
只是封裝receiveMessage
通過添加其拒絕許超時如果時間t
過去之前receiveMessage
解析。
但是如何構造它以便外部用戶可以取消序列? 您有使用承諾而不是輪詢的正確想法。 讓我們編寫我們自己的cancelablePromise
:
function cancelablePromise(executor, canceler) {
return new Promise((resolve, reject) => {
canceler.then(e => reject(`cancelled for reason ${e}`));
executor(resolve, reject);
});
}
我們通過了“執行人”和“取消人”。 “執行程序”是傳遞給Promise構造函數的參數的技術術語,該函數帶有簽名(resolve, reject)
。 我們傳入的取消器是一個諾言,當實現時,它取消(拒絕)我們正在創建的諾言。 因此cancelablePromise
工作原理與new Promise
完全一樣,只是增加了第二個參數,即用於取消的承諾。
現在,您可以根據需要取消的時間,按如下所示編寫代碼:
var canceler1 = new Promise(resolve =>
document.getElementById("cancel1", "click", resolve);
);
send(msg1)
.then(() => cancelablePromise(receiveMessage, canceler1))
.then(() => send(msg2))
.then(() => cancelablePromise(receiveMessage, canceler2))
.catch(() => console.log("Didn't complete sequence"));
如果您正在ES6中編程並且喜歡使用類,則可以編寫
class CancelablePromise extends Promise {
constructor(executor, canceler) {
super((resolve, reject) => {
canceler.then(reject);
executor(resolve, reject);
}
}
這顯然將用於
send(msg1)
.then(() => new CancelablePromise(receiveMessage, canceler1))
.then(() => send(msg2))
.then(() => new CancelablePromise(receiveMessage, canceler2))
.catch(() => console.log("Didn't complete sequence"));
如果使用TypeScript進行編程,則使用上述代碼,您可能需要針對ES6,並在對ES6友好的環境中運行生成的代碼,該環境可以正確處理諸如Promise
之類的內置子類。 如果以ES5為目標,則TypeScript發出的代碼可能不起作用。
上面的方法有一個輕微的(?)缺陷。 即使在我們開始序列之前 cancelablePromise(receiveMessage, canceler1)
canceler
已經實現,或者調用cancelablePromise(receiveMessage, canceler1)
,盡管諾言仍將按預期被取消(拒絕),但是執行器仍將運行,開始接收邏輯-最好這種情況可能會消耗我們不希望的網絡資源。 解決這個問題留作練習。
但是上述方法都沒有解決真正的問題:取消正在進行的異步計算。 這種情況促使人們提出了取消承諾的提議,包括最近從TC39流程中撤回的提議。 假定該計算提供了一些取消它的接口,例如xhr.abort()
。
假設我們有一個網絡工作者來計算第n個素數,該素數在收到go
消息后開始:
function findPrime(n) {
return new Promise(resolve => {
var worker = new Worker('./find-prime.js');
worker.addEventListener('message', evt => resolve(evt.data));
worker.postMessage({cmd: 'go', n});
}
}
> findPrime(1000000).then(console.log)
< 15485863
我們可以把這個取消,假設工人響應一個"stop"
消息終止其工作,再次使用canceler
承諾,這樣做:
function findPrime(n, canceler) {
return new Promise((resolve, reject) => {
// Initialize worker.
var worker = new Worker('./find-prime.js');
// Listen for worker result.
worker.addEventListener('message', evt => resolve(evt.data));
// Kick off worker.
worker.postMessage({cmd: 'go', n});
// Handle canceler--stop worker and reject promise.
canceler.then(e => {
worker.postMessage({cmd: 'stop')});
reject(`cancelled for reason ${e}`);
});
}
}
相同的方法可以用於網絡請求,例如,取消將涉及調用xhr.abort()
。
順便說一句,用於處理這種情況的一個相當優雅的提案(?),即知道如何取消自身的諾言,是讓執行者返回其通常被忽略的返回值,而返回一個可以用來取消的函數本身。 在這種方法下,我們將如下編寫findPrime
執行程序:
const findPrimeExecutor = n => resolve => {
var worker = new Worker('./find-prime.js');
worker.addEventListener('message', evt => resolve(evt.data));
worker.postMessage({cmd: 'go', n});
return e => worker.postMessage({cmd: 'stop'}));
}
換句話說,我們只需要對執行程序進行一次更改即可: return
語句,它提供了一種取消正在進行的計算的方法。
現在我們可以編寫一個通用版本的cancelablePromise
,我們將其稱為cancelablePromise2
,它知道如何與這些特殊的執行程序一起使用,這些執行程序返回一個函數來取消進程:
function cancelablePromise2(executor, canceler) {
return new Promise((resolve, reject) => {
var cancelFunc = executor(resolve, reject);
canceler.then(e => {
if (typeof cancelFunc === 'function') cancelFunc(e);
reject(`cancelled for reason ${e}`));
});
});
}
假設有一個取消器,您的代碼現在可以寫成類似
var canceler = new Promise(resolve => document.getElementById("cancel", "click", resolve);
function chain(msg1, msg2, canceler) {
const send = n => () => cancelablePromise2(findPrimeExecutor(n), canceler);
const receive = () => cancelablePromise2(receiveMessage, canceler);
return send(msg1)()
.then(receive)
.then(send(msg2))
.then(receive)
.catch(e => console.log(`Didn't complete sequence for reason ${e}`));
}
chain(msg1, msg2, canceler);
在用戶點擊“取消”按鈕,瞬間canceler
承諾的兌現,任何懸而未決的發送將被取消,與工人在中途停止,和/或任何掛起的接收將被取消,並承諾會被拒絕,那拒絕順着連鎖反應順流而下,直到最后一catch
。
已經提出了可取消的承諾的各種方法試圖使上述內容更加精簡,更靈活和更實用。 僅舉一個例子,其中一些允許同步檢查取消狀態。 要做到這一點,他們中的一些使用“取消標記”,它可以傳遞,打有點類似於我們的一個角色的概念canceler
承諾。 但是,在大多數情況下,如我們在此處所做的那樣,可以在純用戶域代碼中處理取消邏輯而不會帶來太多復雜性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.