<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 附錄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*。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看