上篇 JavaScript異步進化史:Callbacks,Promises,Async

原文 The Evaluation of Async JavaScript :From Callbacks, to Promises, to Async/Await - (https://tylermcginnis.com/async-javascript-from-callbacks-to-promises-to-async-await/)

我有一個喜歡的網站是BerkshireHathaway.com,它簡單,有效,並且自1997年上線以來一直很好地運行著。值得注意的是,在過去的20年裡,這個網站從來沒有出現過bug。 為什麼? 因為它是全靜態的,它和20年前幾乎一樣,沒有變化。 如果你預先就能擁有所有的數據,那麼構建網站應該是非常簡單。 遺憾的是,現在的大多數網站都沒有。 為了彌補這一點,我們發明了“模式”來處理我們的應用程序取得的外部數據。 像大多數事物一樣,這些模式都隨著時間的推移而發生著變化。 在這篇文章中,我們將分析三種最常見模式的優缺點,即Callbacks,Promises和Async / Await,並從歷史背景中談談它們的意義和發展。

讓我們從這些數據獲取模式中的老炮開始——Callbacks。

Callbacks

我需要先確定你對Callbacks一無所知,如果我猜錯了的話,你只需把頁面再向下滾一截。

當我第一次學習編程時,將函數想象為機器對我來說很有幫助。 這些機器可以做任何你想要它做的事情。 他們可以接受輸入並返回一個值。每臺機器上都有一個按鈕,你可以在需要機器運行時按下該按鈕, ()。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

無論是我按下按鈕,你按下按鈕,還是別人按下按鈕都沒關係。 無論何時只要這個按鈕被按下,機器都將運行。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

在上面的代碼中,我們將add函數分配給三個不同的變量,me,you和someoneElse。 重要的是,這裡需要注意到原始的add和我們創建的每個變量都指向內存中的相同位置。在它們不同的名稱下實際上是同一個東西。 因此,當我們調用me,you或者someoneElse時,就跟我們在調用add一樣。

現在,如果我們將add這個函數傳遞給另一個函數會怎樣? 請記住,是誰按下()這個按鈕並不重要,重要的是如果它被按下,機器就將會運行。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

你的大腦可能在這一點上有點疑惑,但這裡沒有新知識。 我們只是不再在add上“按下按鈕”,而是將add作為參數傳遞給addFive,將其重命名為addReference,然後我們“按下按鈕”調用它。

這突出了JavaScript語言的一些重要概念。首先,正如你可以將字符串或數字作為參數傳遞一樣,你也可以將函數的引用作為參數傳遞。 當你這樣操作的時候,作為參數傳遞的函數被稱為回調函數,而接受回調函數作為參數的函數稱為高階函數。

因為命名很重要,所以這裡重命名代碼裡的變量名,以匹配他們所示範的概念。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

這種模式看起來應該很熟悉,它無處不在。如果你曾經使用過任何JavaScript中的Array方法,那麼你就已經使用過了回調。如果你曾經使用過lodash,那麼你就已經使用過了回調。如果你曾經使用過jQuery,那麼你就已經使用過了回調。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

通常,回調有兩種常見的用法。第一個,也就是我們在.map和_.filter示例中所看到的一樣,是將一個值轉化為另一個值的抽象過程。我們說“嘿,這裡有一個數組和一個函數。來吧,根據我給你的函數給我一個新的值”。第二個,也就是我們在jQuery示例中所看到的,是將函數的執行延遲到特定的時間。 “嘿,這個函數。每當一個id是btn的元素被點擊時,請執行它”。 這第二個用法就是我們所要關注的,”

將函數的執行延遲到特定的時間“。

現在我們只看了同步的例子。正如我們在文章開頭所討論的那樣,我們構建的大多數應用程序在一開始都沒有擁有全部所需的數據。相反,它們需要在與用戶交互的過程中獲取外部數據。我們剛剛看到回調能成為一個很好的方案的原因,再次強調,是因為它們允許你“將函數的執行延遲到特定的時間”。該如何使用該方法來獲取數據沒有太多的懸念,我們可以延遲函數的執行,然後用“獲取到所需的數據”來替代“特定時間”這個條件。這可能是最流行的例子,jQuery的getJSON方法。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

在獲取到數據之前,我們無法更新應用的UI。 那麼我們該怎麼辦? 我們說,“嘿,這是一個對象。 如果請求成功,請調用success方法將取得的數據傳遞給它。 如果沒有成功,請調用error方法將錯誤對象傳遞給它”。 你不需要操心每個方法做了什麼,只需確保在你認為該調用的時候調用它們。 這是使用異步請求回調的完美演示。

此時,我們已經學習到了回調是什麼以及它在同步和異步代碼中的用處。 我們還沒有談到的是回調的黑暗面。 請看下面的代碼。 你能說出發生了什麼嗎?

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

你可以實際動手操作一下。

注意,我們添加了一些回調層。首先我們要求在id為btn的元素被點擊之前不要初始化AJAX請求。一旦按鈕被點擊後,我們會發出第一個請求。如果該請求成功,我們會發出第二個請求。如果該請求成功,我們調用updateUI方法並將從兩個請求中獲取到的數據傳遞給它。不管你是否能瞥一眼就理解了上述代碼,客觀地說它比以前的代碼更難閱讀了。這將我們帶到“回調地獄”的主題。

作為人類,我們天然就是順序化的思考。當你在回調中嵌套回調時,它會強迫你超出你自然的思維方式。當你的軟件閱讀方式與自然思考方式之間存在脫節時,bug就產生了。

像大多數軟件問題的解決方案一樣,更好消化“回調地獄”的常用方法是模塊化你的代碼。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

好吧,函數名稱可以幫助我們瞭解發生了什麼,但客觀上是“更好”了嗎?貌似也不是很好啊。我們在“回調地獄”的可讀性問題上貼了一個創可貼。但是傷口仍然存在,即使藉助額外的函數名稱,嵌套的回調也會使我們脫離順序化的思維方式。

第二個回調的問題與控制轉化有關。當你編寫一個回調時,你假定你傳遞迴調的程序是有效工作著的,並且會在它應該調用的時候(而且只有這個時候能)調用它。實際上,你是潛在地將你程序的控制權轉移到了另一個程序。當你使用jQuery,lodash或vanilla等JavaScript庫時,可以假定它們會安全地使用正確的參數在正確的時間調用回調函數。但是,對於許多第三方庫,回調函數是你與它們交互的接口。第三方庫無論是故意的還是偶然的,都可以打破它們與你的回調交互的方式。

上篇 JavaScript異步進化史:Callbacks,Promises,Async/Await

因為你不是那個調用criticalFunction的人,所以你無法控制什麼時候調用、引用了什麼參數。 大多數時候這不成問題,但是萬一它出現問題時,會是一個非常大的問題。

未完待續

轉載自我的百家號(http://baijiahao.baidu.com/builder/preview/s?id=1616282534311983211)


分享到:


相關文章: