翻譯連載 | 第 10 章:異步的函數式(上)-《JavaScript輕量級函數式編程》 |《你不知道的JS》姊妹篇

關於譯者:這是一個流淌着滬江血液的純粹工程:認真,是 HTML 最堅實的樑柱;分享,是 CSS 裏最閃耀的一瞥;總結,是 JavaScript 中最嚴謹的邏輯。通過捶打磨練,成就了本書的中文版。本書包含了函數式編程之精髓,但願能夠幫助你們在學習函數式編程的道路上走的更順暢。比心。前端

譯者團隊(排名不分前後):阿希bluekenbrucechamcfanlifedailkyoko-dfl3velilinsLittlePineappleMatildaJin冬青pobusamaCherry蘿蔔vavd317vivaxy萌萌zhouyaogit

第 10 章:異步的函數式(上)

閱讀到這裏,你已經學習了我所說的全部輕量級函數式編程的基礎概念,在本章節中,咱們將把這些概念應有到不一樣的情景當中,但絕對不會有新的知識點。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 給 = 操做符擴展隨時間動態賦值的功能,經過可靠的(時間無關)方式。

接下來咱們將探索如何以相同的方式,在時間上異步地拓展本書以前同步的函數式編程操做。

積極的 vs 惰性的

積極的和惰性的在計算機科學的領域並非表揚或者批評的意思,而是描述一個操做是否當即執行或者是延時執行。

咱們在本例子中看到的函數式編程操做能夠被稱爲積極的,由於它們同步(即時)地操做着離散的即時值或值的列表/結構上的值。

回憶下:

var a = [1,2,3]

var b = a.map( v => v * 2 );

b;            // [2,4,6]

這裏 ab 的映射就是積極的,由於它在執行的那一刻映射了數組 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(..) 的實現沒有被寫出來,是由於它是虛構的方法,是不存在的。若是要實現 ab 之間的惰性的操做,那麼簡單的數組就須要變得更加聰明。

考慮下把 ab 關聯到一塊兒的好處,不管什麼時候何地,你添加一個值進 a 裏,它都將改變且映射到 b 裏。它比同爲聲明式函數式編程的 map(..) 更強大,但如今它能夠隨時地變化,進行映射時你不用知道 a 裏面全部的值。

【上一章】翻譯連載 | 第 9 章:遞歸(下)-《JavaScript輕量級函數式編程》 |《你不知道的JS》姊妹篇

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

iKcamp官網:http://www.ikcamp.com

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息