關於譯者:這是一個流淌着滬江血液的純粹工程:認真,是 HTML 最堅實的樑柱;分享,是 CSS 裏最閃耀的一瞥;總結,是 JavaScript 中最嚴謹的邏輯。通過捶打磨練,成就了本書的中文版。本書包含了函數式編程之精髓,但願能夠幫助你們在學習函數式編程的道路上走的更順暢。比心。前端
譯者團隊(排名不分前後):阿希、blueken、brucecham、cfanlife、dail、kyoko-df、l3ve、lilins、LittlePineapple、MatildaJin、冬青、pobusama、Cherry、蘿蔔、vavd317、vivaxy、萌萌、zhouyaogit
閱讀到這裏,你已經學習了我所說的全部輕量級函數式編程的基礎概念,在本章節中,咱們將把這些概念應有到不一樣的情景當中,但絕對不會有新的知識點。github
到目前爲止,咱們所說的一切都是同步的,意味着咱們調用函數,傳入參數後立刻就會獲得返回值。大部分的狀況下是沒問題的,但這幾乎知足不了現有的 JS 應用。爲了能在當前的 JS 環境裏使用上函數式編程,咱們須要去了解異步的函數式編程。編程
本章的目的是拓展咱們對用函數式編程管理數據的思惟,以便以後咱們在更多的業務上應用。數組
在你全部的應用裏,最複雜的狀態就是時間。當你操做的數據狀態改變過程比較直觀的時候,是很容易管理的。可是,若是狀態隨着時間由於響應事件而隱晦的變化,管理這些狀態的難度將會成幾何級增加。promise
咱們在本文中介紹的函數式編程可讓代碼變得更可讀,從而加強了可靠性和可預見性。可是當你添加異步操做到你的項目裏的時候,這些優點將會大打折扣。app
必須明確的一點是:並非說一些操做不能用同步來完成,或者觸發異步行爲很容易。協調那些可能會改變應用程序的狀態的響應,這須要大量額外的工做。異步
因此,做爲做者的你最好付出一些努力,或者只是留給閱讀你代碼的人一個難題,去弄清楚若是 A 在 B 以前完成,項目中狀態是什麼,還有相反的狀況是什麼?這是一個浮誇的問題,但以個人觀點來看,這有一個確切的答案:若是能夠把複雜的代碼變得更容易理解,做者就必須花費更多心思。函數式編程
異步編程最爲重要的一點是經過抽象時間來簡化狀態變化的管理。異步編程
爲說明這一點,讓咱們先來看下一種有競爭狀態(又稱,時間複雜度)的糟糕狀況,且必須手動去管理裏面的狀態:
var customerId = 42; var customer; lookupCustomer( customerId, function onCustomer(customerRecord){ var orders = customer ? customer.orders : null; customer = customerRecord; if (orders) { customer.orders = orders; } } ); lookupOrders( customerId, function onOrders(customerOrders){ if (!customer) { customer = {}; } customer.orders = customerOrders; } );
回調函數 onCustomer(..)
和 onOrders(..)
之間是互爲競爭關係。假設他們都在運行,二者都有可能先運行,那將沒法預測到會發生什麼。
若是咱們能夠把 lookupOrders(..)
寫到 onCustomer(..)
裏面,那咱們就能夠確認 onOrders(..)
會在 onCustomer(..)
以後運行,但咱們不能這麼作,由於咱們須要讓 2 個查詢同時執行。
因此,爲了讓這個基於時間的複雜狀態正常化,咱們用相應的 if
-聲明在各自的回調函數裏來檢查外部做用域的變量 customer
。當各自的回調函數被執行,將會去檢測 customer
的狀態,從而肯定各自的執行順序,若是 customer
在回調函數裏還沒被定義,那他就是先運行的,不然則是第二個運行的。
這些代碼能夠運行,可是他違背了可讀性的原則。時間複雜度讓這個代碼變得難以閱讀。
讓咱們改用 JS promise 來把時間因素抽離出來:
var customerId = 42; var customerPromise = lookupCustomer( customerId ); var ordersPromise = lookupOrders( customerId ); customerPromise.then( function onCustomer(customer){ ordersPromise.then( function onOrders(orders){ customer.orders = orders; } ); } );
如今 onOrders(..)
回調函數存在 onCustomer(..)
回調函數裏,因此他們各自的執行順序是能夠保證的。在各自的 then(..)
運行以前 lookupCustomer(..)
和 lookupOrders(..)
被分別的調用,兩個查詢就已經並行的執行完了。
這可能不太明顯,可是這個代碼裏還有其餘內在的競爭狀態,那就是 promise 的定義沒有被體現出來。若是 orders
的查詢在把 onOrders(..)
回調函數被 ordersPromise.then(..)
調用前完成,那麼就須要一些比較智能的 東西 來保存 orders
直到 onOrders(..)
能被調用。 同理,record
(或者說customer
)對象是否能在 onCustomer(..)
執行時被接收到。
這裏的 東西 和咱們以前討論過的時間複雜度相似。但咱們沒必要去擔憂這些複雜性,不管是編碼或者是讀(更爲重要)這些代碼的時候,由於對咱們來講,promise 所處理的就是時間複雜度上的問題。
promise 以時間無關的方式來做爲一個單一的值。此外,獲取 promise 的返回值是異步的,但倒是經過同步的方法來賦值。或者說, promise 給 =
操做符擴展隨時間動態賦值的功能,經過可靠的(時間無關)方式。
接下來咱們將探索如何以相同的方式,在時間上異步地拓展本書以前同步的函數式編程操做。
積極的和惰性的在計算機科學的領域並非表揚或者批評的意思,而是描述一個操做是否當即執行或者是延時執行。
咱們在本例子中看到的函數式編程操做能夠被稱爲積極的,由於它們同步(即時)地操做着離散的即時值或值的列表/結構上的值。
回憶下:
var a = [1,2,3] var b = a.map( v => v * 2 ); b; // [2,4,6]
這裏 a
到 b
的映射就是積極的,由於它在執行的那一刻映射了數組 a
裏的全部的值,而後生成了一個新的數組 b
。即便以後你去修改 a
,好比說添加一個新的值到數組的最後一位,也不會影響到 b
的內容。這就是積極的函數式編程。
可是若是是一個惰性的函數式編程操做呢?思考以下狀況:
var a = []; var b = mapLazy( a, v => v * 2 ); a.push( 1 ); a[0]; // 1 b[0]; // 2 a.push( 2 ); a[1]; // 2 b[1]; // 4
咱們能夠想象下 mapLazy(..)
本質上 「監聽」 了數組 a
,只要一個新的值添加到數組的末端(使用 push(..)
),它都會運行映射函數 v => v * 2
並把改變後的值添加到數組 b
裏。
注意: mapLazy(..)
的實現沒有被寫出來,是由於它是虛構的方法,是不存在的。若是要實現 a
和 b
之間的惰性的操做,那麼簡單的數組就須要變得更加聰明。
考慮下把 a
和 b
關聯到一塊兒的好處,不管什麼時候何地,你添加一個值進 a
裏,它都將改變且映射到 b
裏。它比同爲聲明式函數式編程的 map(..)
更強大,但如今它能夠隨時地變化,進行映射時你不用知道 a
裏面全部的值。
【上一章】翻譯連載 | 第 9 章:遞歸(下)-《JavaScript輕量級函數式編程》 |《你不知道的JS》姊妹篇
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。
iKcamp官網:http://www.ikcamp.com