# 附錄A:?*asynquence*?庫
第一章和第二章相當詳細地探討了常見的異步編程模式,以及如何通過回調解決它們。但我們也看到了為什么回調在處理能力上有著致命的缺陷,這將我們帶到了第三章和第四章,Promise 與 Generator 為你的異步流程構建提供了一個更加堅實,可信,以及可推理的基礎。
我在這本書中好幾次提到我自己的異步庫?*asynquence*?([http://github.com/getify/asynquence](http://github.com/getify/asynquence)) —— “async” + “sequence” = “asynquence”,現在我想簡要講解一下它的工作原理,以及它的獨特設計為什么很重要和很有用。
在下一篇附錄中,我們將要探索一些高級的異步模式,但為了它們的可用性能夠使人接受你可能需要一個庫。我們將使用?*asynquence*?來表達這些模式,所以你會想首先在這里花一點時間來了解這個庫。
*asynquence*?絕對不是優秀異步編碼的唯一選擇;在這方面當然有許多了不起的庫。但是?*asynquence*?提供了一種獨特的視角 —— 通過將這些模式中最好的部分組合進一個單獨的庫,另外它基于一個基本的抽象:(異步)序列。
我的前提是,精巧的JS程序經常或多或少地需要將各種不同的異步模式交織在一起,而且這通常是完全依靠每個開發者自己去搞清楚的。與其引入關注于異步流程的不同方面的兩個或更多的庫,*asynquence*?將它們統一為各種序列步驟,成為單獨一個需要學習和部署的核心庫。
我相信?*asynquence*?有足夠高的價值可以使 Promise 風格的異步流程控制編程變得超級容易完成,這就是我們為什么會在這里單單關注這個庫。
開始之前,我將講解?*asynquence*?背后的設計原則,然后我們將使用代碼示例來展示它的API如何工作。
## 序列,抽象設計
對?*asynquence*?的理解開始于對一個基礎抽象的理解:對于一個任務的任何一系列步驟來說,無論它們是同步的還是異步的,都可以被綜合地考慮為一個“序列(sequence)”。換句話說,一個序列是一個容器,它代表一個任務,并由一個個完成這個任務的獨立的(可能是異步的)步驟組成。
在這個序列中的每一個步驟都處于一個 Promise(見第三章) 的控制之下。也就是你向一個序列添加的每一個步驟都隱含地創建了一個 Promise,它被鏈接到這個序列的末尾。由于 Promise 的語義,在一個序列中的每一個步驟的推進都是異步的,即使你同步地完成這個步驟。
另外,一個序列將總是一步一步線性地進行,也就是步驟2總是發生在步驟1完成之后,如此類推。
當然,一個新的序列可以從既存的序列中分支出來,也就是分支僅在主序列在流程中到達那一點時發生。序列還可以用各種方式組合,包括使一個序列在流程中的一個特定的位置匯合另一個序列。
一個序列與 Promise 鏈有些相像。但是,在 Promise 鏈中,不存在一個可以引用整個鏈條的“把手”可以抓住。不管你持有哪一個 Promise 的引用,它都表示鏈條中當前的步驟外加掛載在它后面的其他步驟。實質上,你無法持有一個 Promise 鏈條的引用,除非你持有鏈條中第一個 Promise 的引用。
許多情況表明,持有一個綜合地指向整個序列的引用是十分有用的。這些情況中最重要的一種就是序列的退出/取消。正如我們在第三章中展開談過的那樣,Promise 本身絕不應當是可以取消的,因為這違反了一個基本設計規則:外部不可變性。
但是序列沒有這樣的不可變性設計原則,這主要是由于序列不會作為需要不可變語義的未來值的容器被傳遞。所以序列是一個處理退出/取消行為的恰當的抽象層面。*asynquence*?序列可以在任何時候`abort()`,而且這個序列將會停止在那一點而不會因為任何原因繼續下去。
為了流程控制,還有許多理由首選序列的抽象而非 Promise 鏈。
首先,Promise 鏈是一個更加手動的處理 —— 一旦你開始在你的程序中大面積地創建和鏈接 Promise ,這種處理可能會變得相當煩冗 —— 在那些使用 Promise 相當恰當的地方,這種煩冗會降低效率而使得開發者不愿使用Promise。
抽象意味著減少模板代碼和煩冗,所以序列抽象是這個問題的一個好的解決方案。使用 Promise,你關注的是個別的步驟,而且不太會假定你將延續這個鏈條。而序列采用相反的方式,它假定序列將會無限地持續添加更多步驟。
當你開始考慮更高階的 Promise 模式時(除了`race([..])`和`all([..])`以外),這種抽象復雜性的降低特別強大。
例如,在一個序列的中間,你可能想表達一個在概念上類似于`try..catch`的步驟,它的結果將總是成功,不管是意料之中的主線上的成功解析,還是為被捕獲的錯誤提供一個正面的非錯誤信號。或者,你可能想表達一個類似于 retry/until 循環的步驟,它不停地嘗試相同的步驟直到成功為止。
僅僅使用基本的 Promise,這類抽象不是很容易表達,而且在一個既存的 Promise 鏈的中間這樣做不好看。但如果你將你的想法抽象為一個序列,并將一個步驟考慮為一個 Promise 的包裝,這個包裝可以隱藏這樣的細節,它就可以使你以最合理的方式考慮流程控制,而不必關心細節。
第二,也許是更重要的,將異步流程控制考慮為一個序列中的步驟,允許你將這樣的細節抽象出去 —— 每一個步驟中引入了哪一種異步性。在這種抽象之下,一個 Promise 將總是控制著步驟,但在抽象之上,這個步驟可以看起來像一個延續回調(簡單的默認值),或者一個真正的 Promise,或者一個運行至完成的 Generator,或者... 希望你明白我的意思。
第三,序列可以通容易地被調整來適應于不同的思考模式,比如基于事件的,基于流的,或者基于相應式的編碼。*asynquence*提供了一種我稱為“響應式序列”的模式(我們稍后講解),它是 RxJS(“Reactive Extensions”) 中“響應式可監聽”思想的變種,允許重復的事件每次觸發一個新的序列實例。Promise 是一次性的,所以單獨使用 Promise 來表達重復的異步性十分尷尬。
在一種我稱為“可迭代序列”的模式中,另一種思考模式反轉了解析/控制能力。與每一個步驟在內部控制它自己的完成(并因此推進這個序列)不同,序列被反轉為通過一個外部迭代器來進行推進控制,而且在這個?*可迭代序列*?中的每一步僅僅應答`next(..)`*迭代器*?控制。
在本附錄的剩余部分,我們將探索所有這些不同的種類,所以如果我們剛才的步伐太快也不要擔心。
要點是,對于復雜的異步處理來說,序列是一個要比單純的 Promise(Promise鏈)或單純的 Generator 更加強大與合理的抽象,而?*asynquence*?被設計為使用恰當層面的語法糖來表達這種抽象,使得異步編程變得更加易于理解和更加令人愉快。
## *asynquence*?API
首先,你創建一個序列(一個?*asynquence*?實例)的方法是使用`ASQ(..)`函數。一個不帶參數的`ASQ()`調用會創建一個空的初始序列,而向`ASQ(..)`傳遞一個或多個值或函數的話,它會使用每個參數值代表序列的初始步驟來創建序列。
注意:?為了這里所有的代碼示例,我將使用?*asynquence*?在瀏覽器全局作用域中的頂層標識符:`ASQ`。如果你通過一個模塊系統(在瀏覽器或服務器中)引入并使用?*asynquence*,你當然可以定義自己喜歡的符號,*asynquence*?不會關心這些!
許多在這里討論的API方法都內建于?*asynquence*?的核心部分,而其他的API是通過引入可選的“contrib”插件包提供的。要知道一個方法是內建的還是通過插件定義的,可以參見?*asynquence*?的文檔:[http://github.com/getify/asynquence](http://github.com/getify/asynquence)
### 步驟
如果一個函數代表序列中的一個普通步驟,那么這個函數會被這樣調用:第一個參數是延續回調,而任何后續參數都是從前一個步驟中傳遞下來的消息。在延續回調被調用之前,這個步驟將不會完成。一旦延續回調被調用,你傳遞給它的任何參數值都會作為序列下一個步驟中的消息被發送。
要向一個序列添加額外的普通步驟,調用`then(..)`(它實質上與`ASQ(..)`調用的語義完全相同):
```source-js
ASQ(
// 步驟 1
function(done){
setTimeout( function(){
done( "Hello" );
}, 100 );
},
// 步驟 2
function(done,greeting) {
setTimeout( function(){
done( greeting + " World" );
}, 100 );
}
)
// 步驟 3
.then( function(done,msg){
setTimeout( function(){
done( msg.toUpperCase() );
}, 100 );
} )
// 步驟 4
.then( function(done,msg){
console.log( msg ); // HELLO WORLD
} );
```
注意:?雖然`then(..)`這個名稱與原生的 Promise API 完全一樣,但是這個`then(..)`的含義是不同的。你可以傳遞任意多或者任意少的函數或值給`then(..)`,而它們中的每一個都被看作是一個分離的步驟。這里與完成/拒絕語義的雙回調毫不相干。
在 Promise 中,可以把一個 Promise 與下一個你在`then(..)`的完成處理器中創建并`return`的 Promise 鏈接。與此不同的是,在?*asynquence*?中,你所需要做的一切就是調用延續回調 —— 我總是稱之為`done()`,但你可以起任何適合你的名字 —— 并將完成的消息作為參數值選擇性地傳遞給它。
通過`then(..)`定義的每一個步驟都被認為是異步的。如果你有一個同步的步驟,你可以立即調用`done(..)`,或者使用更簡單的`val(..)`步驟幫助函數:
```source-js
// 步驟 1(同步)
ASQ( function(done){
done( "Hello" ); // 手動同步
} )
// 步驟 2(同步)
.val( function(greeting){
return greeting + " World";
} )
// 步驟 3(異步)
.then( function(done,msg){
setTimeout( function(){
done( msg.toUpperCase() );
}, 100 );
} )
// 步驟 4(同步)
.val( function(msg){
console.log( msg );
} );
```
如你所見,`val(..)`調用的步驟不會收到一個延續回調,因為這部分已經為你做好了 —— 而且參數列表作為一個結果顯得不那么凌亂了!要向下一個步驟發送消息,你簡單地使用`return`。
將`val(..)`考慮為表示一個同步的“僅含有值”的步驟,它對同步的值操作,比如 logging 之類,非常有用。
### 錯誤
與 Promise 相比?*asynquence*?的一個重要的不同之處是錯誤處理。
在 Promise 鏈條中,每個 Promise(步驟)都可以擁有自己獨立的錯誤,而每個后續的步驟都有能力處理或不處理這個錯誤。這種語義(再一次)主要來自于對每個單獨的 Promise 的關注,而非對整個鏈條(序列)的關注。
我相信,在大多數情況下,一個位于序列中某一部分的錯誤通常是不可恢復的,所以序列中后續的步驟毫無意義而應當被跳過。所以,默認情況下,在一個序列的任意一個步驟中的錯誤會將整個序列置于錯誤模式,而剩下的普通步驟將會被忽略。
如果你?*確實*?需要一個錯誤可以被恢復的步驟,有幾個不同的API可以適應這種情況,比如`try(..)`?—— 先前提到過的,有些像`try..catch`的步驟 —— 或者`until(..)`?—— 一個重試循環,它持續地嘗試一個步驟直到它成功或你手動地`break()`這個循環。*asynquence*?甚至擁有`pThen(..)`和`pCatch(..)`方法,它們的工作方式與普通的 Promise 的`then(..)`和`catch(..)`(見第三章)完全相同,所以如果你選擇這么做,你就可以進行本地化的序列中錯誤處理。
重點是,你同時擁有兩個選項,但是在我的經驗中更常見的是默認情況。使用 Promise,要使一個步驟的鏈條在錯誤發生時一次性忽略所有步驟,你不得不小心不要在任何步驟中注冊拒絕處理器;否則,這個錯誤會被視為處理過而被吞掉,而序列可能仍會繼續下去(也許不是意料之中的)。要恰當且可靠地處理這種期待的行為有點兒尷尬。
要注冊一個序列錯誤通知處理器,*asynquence*?提供了一個`or(..)`序列方法,它還有一個別名叫做`onerror(..)`。你可以在序列的任何位置調用這個方法,而且你可以注冊任意多的處理器。這使得讓多個不同的消費者監聽一個序列是否失敗變得很容易;從這個角度講,它有點兒像一個錯誤事件處理器。
正如使用 Promise 那樣,所有JS異常都會變為序列錯誤,或者你可以通過編程來發生一個序列錯誤:
```source-js
var sq = ASQ( function(done){
setTimeout( function(){
// 為序列發出一個錯誤
done.fail( "Oops" );
}, 100 );
} )
.then( function(done){
// 永遠不會到達這里
} )
.or( function(err){
console.log( err ); // Oops
} )
.then( function(done){
// 也不會到達這里
} );
// 稍后
sq.or( function(err){
console.log( err ); // Oops
} );
```
*asynquence*?與原生的 Promise 相比,在錯誤處理上另一個重要的不同就是“未處理異常”的默認行為。正如我們在第三章中以相當的篇幅討論過的,一個沒有被注冊拒絕處理器的 Promise 如果被拒絕的話,將會無聲地保持(也就是吞掉)那個錯誤;你不得不總是想著要用一個最后的`catch(..)`來終結一個鏈條。
在?*asynquence*?中,這種假設被顛倒過來了。
如果一個錯誤在序列上發生,而且?在那個時刻?它沒有被注冊錯誤處理器,那么這個錯誤會被報告至`console`。換言之,未處理的的拒絕將總是默認地被報告,因此不會被吞掉或丟掉。
為了防止重復的噪音,只要你向一個序列注冊一個錯誤處理器,它就會使這個序列從這樣的報告中退出。
事實上有許多情況你想要創建這樣一個序列,它可能會在你有機會注冊處理器之前就進入錯誤狀態。這不常見,但可能時不時地發生。
在這樣的情況下,你也可以通過在序列上調用`defer()`來使一個序列實例?從錯誤報告中退出。你應當僅在自己確信不會最終處理這樣的錯誤時,才決定從報告中退出:
```source-js
var sq1 = ASQ( function(done){
doesnt.Exist(); // 將會向控制臺拋出異常
} );
var sq2 = ASQ( function(done){
doesnt.Exist(); // 僅僅會拋出一個序列錯誤
} )
// 錯誤報告中的退出
.defer();
setTimeout( function(){
sq1.or( function(err){
console.log( err ); // ReferenceError
} );
sq2.or( function(err){
console.log( err ); // ReferenceError
} );
}, 100 );
// ReferenceError (來自sq1)
```
這是一種比 Promise 本身擁有的更好的錯誤處理行為,因為它是一個成功的深淵,而不是一個失敗的深淵(參見第三章)。
注意:?如果一個序列被導入(也就是被匯合入)另一個序列 —— 完整的描述參見“組合序列” —— 之后源序列從錯誤報告中退出,那么就必須考慮目標序列是否進行錯誤報告。
### 并行步驟
在你的序列中不是所有的步驟都將只擁有一個(異步)任務去執行;有些將會需要“并行”(并發地)執行多個步驟。在一個序列中,一個并發地處理多個子步驟的步驟稱為一個`gate(..)`?—— 如果你喜歡的話它還有一個別名`all(..)`?—— 而且它與原生的`Promise.all([..])`是對稱的。
如果在`gate(..)`中的所有步驟都成功地完成了,那么所有成功的消息都將被傳遞到下一個序列步驟中。如果它們中的任何一個產生了一個錯誤,那么整個序列會立即進入錯誤狀態。
考慮如下代碼:
```source-js
ASQ( function(done){
setTimeout( done, 100 );
} )
.gate(
function(done){
setTimeout( function(){
done( "Hello" );
}, 100 );
},
function(done){
setTimeout( function(){
done( "World", "!" );
}, 100 );
}
)
.val( function(msg1,msg2){
console.log( msg1 ); // Hello
console.log( msg2 ); // [ "World", "!" ]
} );
```
為了展示差異,讓我們把這個例子與原生 Promise 比較一下:
```source-js
new Promise( function(resolve,reject){
setTimeout( resolve, 100 );
} )
.then( function(){
return Promise.all( [
new Promise( function(resolve,reject){
setTimeout( function(){
resolve( "Hello" );
}, 100 );
} ),
new Promise( function(resolve,reject){
setTimeout( function(){
// 注意:這里我們需要一個 [ ]
resolve( [ "World", "!" ] );
}, 100 );
} )
] );
} )
.then( function(msgs){
console.log( msgs[0] ); // Hello
console.log( msgs[1] ); // [ "World", "!" ]
} );
```
討厭。Promise 需要多得多的模板代碼來表達相同的異步流程控制。這個例子很好地說明了為什么?*asynquence*?API 和抽象使得對付 Promise 步驟容易多了。你的異步流程越復雜,它的改進程度就越高。
#### 各種步驟
關于?*asynquence*?的`gate(..)`步驟類型,有好幾種不同的 contrib 插件可能十分有用:
* `any(..)`很像`gate(..)`,除了為了繼續主序列,只需要有一個環節最終必須成功。
* `first(..)`很像`any(..)`,除了只要有任何一個環節成功,主序列就會繼續(忽略任何其余環節產生的后續結果)。
* `race(..)`(與`Promise.race([..])`對稱)很像`first(..)`,除了主序列會在任何環節完成時(不管成功還是失敗)立即繼續。
* `last(..)`很像`any(..)`,除了只有最后一個環節成功完成時才會把它的消息發送給主序列。
* `none(..)`是`gate(..)`的反義:主序列僅在所有環節失敗時才會繼續(將所有環節的錯誤消息作為成功消息傳送,或者反之)。
讓我們首先定義一些幫助函數來使示例清晰一些:
```source-js
function success1(done) {
setTimeout( function(){
done( 1 );
}, 100 );
}
function success2(done) {
setTimeout( function(){
done( 2 );
}, 100 );
}
function failure3(done) {
setTimeout( function(){
done.fail( 3 );
}, 100 );
}
function output(msg) {
console.log( msg );
}
```
現在,讓我們展示一些這些`gate(..)`步驟的變種:
```source-js
ASQ().race(
failure3,
success1
)
.or( output ); // 3
ASQ().any(
success1,
failure3,
success2
)
.val( function(){
var args = [].slice.call( arguments );
console.log(
args // [ 1, undefined, 2 ]
);
} );
ASQ().first(
failure3,
success1,
success2
)
.val( output ); // 1
ASQ().last(
failure3,
success1,
success2
)
.val( output ); // 2
ASQ().none(
failure3
)
.val( output ) // 3
.none(
failure3
success1
)
.or( output ); // 1
```
另一個步驟種類是`map(..)`,它讓你將一個數組的元素異步地映射為不同的值,而且在所有映射完成之前步驟不會前進。`map(..)`與`gate(..)`十分相似,除了它從一個數組,而非從一個指定的分離函數那里得到初始值,而且你定義一個函數回調來操作每一個值:
```source-js
function double(x,done) {
setTimeout( function(){
done( x * 2 );
}, 100 );
}
ASQ().map( [1,2,3], double )
.val( output ); // [2,4,6]
```
另外,`map(..)`可以從前一步驟傳遞來的消息中收到它的兩個參數(數組或者回調):
```source-js
function plusOne(x,done) {
setTimeout( function(){
done( x + 1 );
}, 100 );
}
ASQ( [1,2,3] )
.map( double ) // 收到消息`[1,2,3]`
.map( plusOne ) // 收到消息`[2,4,6]`
.val( output ); // [3,5,7]
```
另一個種類是`waterfall(..)`,它有些像混合了`gate(..)`的消息收集行為與`then(..)`的序列化處理。
步驟1首先被執行,然后來自步驟1的成功消息被傳遞給步驟2,然后兩個成功消息走到步驟3,然后所有三個成功消息走到步驟4,如此繼續,這樣消息被某種程度上收集并從“瀑布”上傾瀉而下。
考慮如下代碼:
```source-js
function double(done) {
var args = [].slice.call( arguments, 1 );
console.log( args );
setTimeout( function(){
done( args[args.length - 1] * 2 );
}, 100 );
}
ASQ( 3 )
.waterfall(
double, // [ 3 ]
double, // [ 6 ]
double, // [ 6, 12 ]
double // [ 6, 12, 24 ]
)
.val( function(){
var args = [].slice.call( arguments );
console.log( args ); // [ 6, 12, 24, 48 ]
} );
```
如果在“瀑布”的任何一點發生錯誤,那么整個序列就會立即進入錯誤狀態。
#### 容錯
有時你想在步驟一級管理錯誤,而不一定讓它們使整個序列成為錯誤狀態。*asynquence*?為此提供了兩種步驟類型。
`try(..)`嘗試一個步驟,如果它成功,序列就會正常繼續,但如果這個步驟失敗了,失敗的狀態會轉換成格式為`{ catch: .. }`的成功消息,它的值由錯誤消息填充:
```source-js
ASQ()
.try( success1 )
.val( output ) // 1
.try( failure3 )
.val( output ) // { catch: 3 }
.or( function(err){
// 永遠不會到達這里
} );
```
你還可以使用`until(..)`構建一個重試循環,它嘗試一個步驟,如果失敗,就會在下一個事件輪詢的 tick 中重試這個步驟,如此繼續。
這種重試循環可以無限延續下去,但如果你想要從循環中跳出來,你可以在完成觸發器上調用`break()`標志方法,它將主序列置為錯誤狀態:
```source-js
var count = 0;
ASQ( 3 )
.until( double )
.val( output ) // 6
.until( function(done){
count++;
setTimeout( function(){
if (count < 5) {
done.fail();
}
else {
// 跳出 `until(..)` 重試循環
done.break( "Oops" );
}
}, 100 );
} )
.or( output ); // Oops
```
#### Promise 式的步驟
如果你喜歡在你的序列中內聯 Promise 風格的語義,比如 Promise 的`then(..)`和`catch(..)`(見第三章),你可以使用`pThen`和`pCatch`插件:
```source-js
ASQ( 21 )
.pThen( function(msg){
return msg * 2;
} )
.pThen( output ) // 42
.pThen( function(){
// 拋出一個異常
doesnt.Exist();
} )
.pCatch( function(err){
// 捕獲這個異常(拒絕)
console.log( err ); // ReferenceError
} )
.val( function(){
// 主旋律回歸到正常狀態,
// 因為前一個異常已經被
// `pCatch(..)`捕獲了
} );
```
`pThen(..)`和`pCatch(..)`被設計為運行在序列中,但好像在普通的 Promise 鏈中動作。這樣,你就可以在傳遞給`pThen(..)`的“完成”處理器中解析純粹的 Promise 或者?*asynquence*?序列。
### 序列分支
一個有關 Promise 的可能十分有用的特性是,你可以在同一個 Promise 上添附多個`then(..)`處理器,這實質上在這個 Promise 的流程上創建了“分支”:
```source-js
var p = Promise.resolve( 21 );
// (從`p`開始的)分支 1
p.then( function(msg){
return msg * 2;
} )
.then( function(msg){
console.log( msg ); // 42
} )
// (從`p`開始的)分支 2
p.then( function(msg){
console.log( msg ); // 21
} );
```
使用?*asynquence*?的`fork()`可以很容易地進行同樣的“分支”:
```source-js
var sq = ASQ(..).then(..).then(..);
var sq2 = sq.fork();
// 分支 1
sq.then(..)..;
// 分支 2
sq2.then(..)..;
```
### 組合序列
與`fork()`相反的是,你可以通過將一個序列匯合進另一個來組合兩個序列,使用`seq(..)`實例方法:
```source-js
var sq = ASQ( function(done){
setTimeout( function(){
done( "Hello World" );
}, 200 );
} );
ASQ( function(done){
setTimeout( done, 100 );
} )
// 將序列 `sq` 匯合進這個系列
.seq( sq )
.val( function(msg){
console.log( msg ); // Hello World
} )
```
`seq(..)`可以像這里展示的那樣接收一個序列本身,或者接收一個函數。如果是一個函數,那么它會期待這個函數被調用時返回一個序列,所以前面的代碼可以這樣寫:
```source-js
// ..
.seq( function(){
return sq;
} )
// ..
```
另外,這個步驟還可以使用`pipe(..)`來完成:
```source-js
// ..
.then( function(done){
// 將 `sq` 導入延續回調 `done`
sq.pipe( done );
} )
// ..
```
當一個序列被匯合時,它的成功消息流和錯誤消息流都會被導入。
注意:?正如早先的注意事項中提到過的,導入會使源序列從錯誤報告中退出,但不會影響目標序列的錯誤報告狀態。
## 值與錯誤序列
如果一個序列的任意一個步驟只是一個普通值,那么這個值就會被映射到這個步驟的完成消息中:
```source-js
var sq = ASQ( 42 );
sq.val( function(msg){
console.log( msg ); // 42
} );
```
如果你想制造一個自動出錯的序列:
```source-js
var sq = ASQ.failed( "Oops" );
ASQ()
.seq( sq )
.val( function(msg){
// 不會到達這里
} )
.or( function(err){
console.log( err ); // Oops
} );
```
你還可能想要自動地創建一個延遲的值或者延遲的錯誤序列。使用`after`和`failAfter`?contrib 插件,這很容易:
```source-js
var sq1 = ASQ.after( 100, "Hello", "World" );
var sq2 = ASQ.failAfter( 100, "Oops" );
sq1.val( function(msg1,msg2){
console.log( msg1, msg2 ); // Hello World
} );
sq2.or( function(err){
console.log( err ); // Oops
} );
```
你還可以使用`after'(..)`在一個序列的中間插入一個延遲:
```source-js
ASQ( 42 )
// 在這個序列中插入一個延遲
.after( 100 )
.val( function(msg){
console.log( msg ); // 42
} );
```
## Promises 與回調
我認為?*asynquence*?序列在原生的 Promise 之上提供了許多價值,而且你會發現在很大程度上它在抽象層面上使用起來更舒適更強大。然而,將?*asynquence*?與其他非?*asynquence*?代碼進行整合將是不可避免的現實。
使用`promise(..)`實例方法,你可以很容易地將一個 Promise(也就是 thenable —— 見第三章)匯合進一個序列:
```source-js
var p = Promise.resolve( 42 );
ASQ()
.promise( p ) // 本可以寫做:`function(){ return p; }`
.val( function(msg){
console.log( msg ); // 42
} );
```
要向相反的方向走,從一個序列的特定步驟中分支/出讓一個 Promise,使用`toPromise`?contrib 插件:
```source-js
var sq = ASQ.after( 100, "Hello World" );
sq.toPromise()
// 現在這是一個標準的 promise 鏈了
.then( function(msg){
return msg.toUpperCase();
} )
.then( function(msg){
console.log( msg ); // HELLO WORLD
} );
```
有好幾種幫助設施可以在使用回調的系統中適配?*asynquence*。要從你的序列中自動地生成一個“錯誤優先風格”回調,來接入一個面向回調的工具,使用`errfcb`:
```source-js
var sq = ASQ( function(done){
// 注意:這里期待“錯誤優先風格”的回調
someAsyncFuncWithCB( 1, 2, done.errfcb )
} )
.val( function(msg){
// ..
} )
.or( function(err){
// ..
} );
// 注意:這里期待“錯誤優先風格”的回調
anotherAsyncFuncWithCB( 1, 2, sq.errfcb() );
```
你還可能想要創建一個工具的序列包裝版本 —— 與第三章的“promisory”和第四章的“thunkory”相比較 ——?*asynquence*?為此提供了`ASQ.wrap(..)`:
```source-js
var coolUtility = ASQ.wrap( someAsyncFuncWithCB );
coolUtility( 1, 2 )
.val( function(msg){
// ..
} )
.or( function(err){
// ..
} );
```
注意:?為了清晰(和有趣!),讓我們為來自`ASQ.wrap(..)`的產生序列的函數杜撰另一個名詞,就像這里的`coolUtility`。我提議“sequory”(“sequence” + “factory”)。
## 可迭代序列
一個序列普通的范例是,每一個步驟都負責完成它自己,進而推進這個序列。Promise 就是這樣工作的。
不幸的是,有時你需要從外部控制一個 Promise/步驟,而這會導致尷尬的“能力抽取”。
考慮這個 Promise 的例子:
```source-js
var domready = new Promise( function(resolve,reject){
// 不想把這個放在這里,因為在邏輯上
// 它屬于代碼的另一部分
document.addEventListener( "DOMContentLoaded", resolve );
} );
// ..
domready.then( function(){
// DOM 準備好了!
} );
```
關于 Promise 的“能力抽取”范模式看起來像這樣:
```source-js
var ready;
var domready = new Promise( function(resolve,reject){
// 抽取 `resolve()` 能力
ready = resolve;
} );
// ..
domready.then( function(){
// DOM 準備好了!
} );
// ..
document.addEventListener( "DOMContentLoaded", ready );
```
注意:?在我看來,這種反模式是一種尷尬的代碼風格,但有些開發者喜歡,我不能理解其中的原因。
*asynquence*?提供一種我稱為“可迭代序列”的反轉序列類型,它將控制能力外部化(它在`domready`這樣的情況下十分有用):
```source-js
// 注意:這里`domready`是一個控制序列的 *迭代器*
var domready = ASQ.iterable();
// ..
domready.val( function(){
// DOM 準備好了!
} );
// ..
document.addEventListener( "DOMContentLoaded", domready.next );
```
與我們在這個場景中看到的東西比起來,可迭代序列還有很多內容。我們將在附錄B中回過頭來討論它們。
## 運行 Generator
在第四章中,我們衍生了一種稱為`run(..)`的工具,它可以將 generator 運行至完成,監聽被`yield`的 Promise 并使用它們來異步推進 generator。*asynquence*?正好有一個這樣的內建工具,稱為`runner(..)`。
為了展示,讓我們首先建立一些幫助函數:
```source-js
function doublePr(x) {
return new Promise( function(resolve,reject){
setTimeout( function(){
resolve( x * 2 );
}, 100 );
} );
}
function doubleSeq(x) {
return ASQ( function(done){
setTimeout( function(){
done( x * 2)
}, 100 );
} );
}
```
現在,我們可以在一個序列的中間使用`runner(..)`作為一個步驟:
```source-js
ASQ( 10, 11 )
.runner( function*(token){
var x = token.messages[0] + token.messages[1];
// yield 一個真正的 promise
x = yield doublePr( x );
// yield 一個序列
x = yield doubleSeq( x );
return x;
} )
.val( function(msg){
console.log( msg ); // 84
} );
```
### 包裝過的 Generator
你還可以創建自包裝的 generator —— 也就是一個普通函數,運行你指定的 generator 并為它的完成返回一個序列 —— 通過`ASQ.wrap(..)`包裝它:
```source-js
var foo = ASQ.wrap( function*(token){
var x = token.messages[0] + token.messages[1];
// yield 一個真正的 promise
x = yield doublePr( x );
// yield 一個序列
x = yield doubleSeq( x );
return x;
}, { gen: true } );
// ..
foo( 8, 9 )
.val( function(msg){
console.log( msg ); // 68
} );
```
`runner(..)`還能做很多很牛的事情,我們會在附錄B中回過頭來討論它。
## 復習
*asynquence*?是一個在 Promise 之上的簡單抽象 —— 一個序列是一系列(異步)步驟,它的目標是使各種異步模式更加容易使用,而在功能上沒有任何妥協。
在?*asynquence*?的核心API與它的 contrib 插件中,除了我們在這篇附錄中看到的內容以外還有其他的好東西,我們把對這些剩余功能的探索作為練習留給讀者。
現在你看到了?*asynquence*?的實質與精神。關鍵點是,一個序列由許多步驟組成,而這些步驟可以使許多不同種類的 Promise,或者它們可以是一個 generator 運行器,或者... 選擇由你來決定,你有完全的自由為你的任務采用恰當的任何異步流程控制邏輯。
如果你能理解這些?*asynquence*?代碼段,那么你現在就可以相當快地學會這個庫;它實際上沒有那么難學!
如果你依然對它如何(或為什么!)工作感到模糊,那么在進入下一篇附錄之前,你將會想要多花一點時間去查看前面的例子,并親自把玩一下?*asynquence*。附錄B將會在幾種更高級更強大的異步模式中使用?*asynquence*。