[翻譯]JavaScript異步進化史:Callbacks,Promises,Async/Await 上

2020-12-17 百家號

原文 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一無所知,如果我猜錯了的話,你只需把頁面再向下滾一截。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

未完待續

喜歡就點讚唄

相關焦點

  • 代碼詳解:Async/Await優於基礎Promises的7大原因
    Async/Await 101 Async/await是一種編寫異步代碼的新方法。以前編寫異步代碼會用callbacks和promises。 Async/await 實際上只是一種基於promises的糖衣語法,不能與基礎callbacks或節點callbacks一同使用。 Async/await和promises一樣,都是非堵塞式的。 Async/await讓異步代碼更具同步代碼風格,這也是其優勢所在。
  • 你必須了解的JavaScript關鍵字async和await
    ,以便可以從異步函數中捕獲和處理awaited promises中的錯誤。txt,並允許消費者使用promises或另一個async函數來繼續。一個包含多個await表達式的函數將在每個await表達式上每次掛起一次,直到該Promise得到解決,然後取消掛起執行並轉到下一個await表達式——這與我們使用生成器和yield觀察到的情況不同。為了解決這個問題,您可以使用Promise.all創建一個您可以等待的Promise。
  • 如何正確合理使用 JavaScript async/await
    async 函數調用不會造成阻塞,它內部所有的阻塞都被封裝在一個 Promise 對象中異步執行。async/await 帶給我們的最重要的好處是同步編程風格。讓我們看一個例子:從本質上說,async 函數仍然是 promise。
  • 如何用實例掌握Async/Await
    今天讓我們一起來探討如何用實例掌握Async/Await目錄1、簡介(callbacks, promises, async/await)2、實例—貨幣轉換器從2個API’s接收異步數據。簡介Async/await是一種編寫異步代碼的新方法。它是建立在promises之上的,所以也是非阻塞。最大的差別在於異步代碼看起來更靠近同步代碼。這就是它的關鍵所在。
  • Async/Await有什麼用?
    本文我們來討論 javascript 世界中最先進的異步編程思想之一。什麼是異步函數javascript 中的異步函數是有著一些「超能力」的常規函數。要定義異步函數,需要在其定義前加上 async 關鍵字。
  • javascript解決異步async、await和co庫的實現
    相信大家都聽說過js中的回調地獄給代碼維護帶來了很大的阻礙,應用而生的也給出了N解決方案,從最初的promise,到co庫,再到es規範提供的api async、await等!接下來咱們聊的話題就是async和co庫的具體實現在學習前咱們了解幾個小知識點吧!
  • 理解JavaScript 的 async/await
    1. async 和 await 在幹什麼任意一個名稱都是有意義的,先從字面意思來理解。async 是「異步」的簡寫,而 await 可以認為是 async wait 的簡寫。所以應該很好理解 async 用於申明一個 function 是異步的,而 await 用於等待一個異步方法執行完成。
  • [完結篇] - 理解異步之美 --- promise與async await(三)
    因為在用法上promise要比async await難一些,而且promise本身又不是一個語法糖。沒有掌握的時候用起來就會有很多顧慮,async await卻沒有這種顧慮,用法簡單、語義清晰。但是javascript並沒有這種結果(ps:ES6提供了set,而且也可以實現迭代器),但是我們對這種模式實用的並不是特別多。迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內部表示。
  • JavaScript中的async/await的用法和理解
    今天就說一說「JavaScript中的async/await的用法和理解」程式語言中任意一個關鍵字都是有意義的,我們先從字面意思來理解。1.async async 是「異步」的簡寫,帶async關鍵字的函數,是聲明異步函數,返回值是promise對象,如果async關鍵字函數返回的不是promise,會自動用Promise.resolve()包裝。
  • 如何在 JS 循環中正確使用 async 與 await
    和getNumFruit來獲取異步函數中每個水果的數量。(注意回調函數中的async關鍵字。我們需要這個async關鍵字,因為await在回調函數中)。在 map 中使用 await如果在map中使用await, map 始終返回promise數組,這是因為異步函數總是返回promise。
  • 壓箱底筆記:Promise和Async/await的理解和使用
    => 純回調的形式)具體表達:從功能上來說:Promise 對象用來封裝一個異步操作並可以獲取其結果2.2 Promise的狀態改變Promise的狀態改變只有這2種:與awaitAsync/await 實際上只是一種基於promises的糖衣語法糖,Async/await 和 promises一樣,都是非堵塞式的,Async/await 讓異步代碼更具同步代碼風格,這也是其優勢所在。
  • async/await,了解一下?
    因此,在 ES6 中封裝了 Generator 函數的語法糖 async 函數,但是將其定義在了 es7 中。ES7 定義出的 async 函數,終於讓 JavaScript 對於異步操作有了終極解決方案。 Async 函數是 Generator 函數的語法糖。使用 關鍵字 Async 來表示,在函數內部使用 await 來表示異步。
  • Python async/await教程
    async/await更新的和更清潔的語法是使用async/await關鍵字,async在Python 3.5中引入,用於作為一個協同程序聲明一個函數,就像@asyncio.coroutine裝飾器所做的,通過把它放到函數定義前使它應用於函數:
  • 深入async/await知多少
    工作原理      async/await簡單來說只是一個語法糧,它只是告訴編譯器要把這些代碼編譯成一個異步狀態機。      async/await是一個異步處理模型,但並不能說明所有的async/await都是異步處理;具體要看Awaiter狀態機是由誰觸發的,當上層方法邏輯是同步或IO同步完成的情況那await後面的代碼則由同當前線程觸發執行,如果上層方法是異步完成的情況下則由對應相關異步完成的線程調用;所以async/await也有些情況是同步完成的,只是這種情況在
  • 拆解 JavaScript 中的異步模式
    callback、promise、generator、async await 甚至 RxJS。我最初接觸不同的異步模式時,曾想當然的覺得 promise 就是比 callback 好, async await 比就是比 promise 優雅,會把它們割裂起來看待。後來發現也不完全這樣,各種異步模式之間其實存在著關聯,也有著各自擅長的場景。
  • 從promise讀懂JavaScript異步編程
    從《setTimeout(fn,0)函數剖析JavaScript的執行機制》一文已經說明了同步和異步的區別,而這篇文章將更深入的去理解什麼是JS的異步編程,以及promise,async,await等的使用。從而更好的為前端編程打好堅實的基礎。
  • C# 中的Async 和 Await 的用法詳解
    同樣本文的內容也大多是翻譯的,只不過加上了自己的理解進行了相關知識點的補充,如果你認為自己的英文水平還不錯,大可直接跳轉到文章末尾查看原文連結進行閱讀。作者:依樂祝原文連結:https://www.cnblogs.com/yilezhu/p/10555849.html寫在前面自從C# 5.0時代引入async和await關鍵字後,異步編程就變得流行起來。
  • 理解異步之美--- Promise與async await(一)
    如果表達有誤的地方,還望評論區指出~不多嗶嗶,坐穩扶好,發車了~你可能會放出一個怪物異步與同步相比,最難以掌控的就是異步的任務會什麼時候完成和完成之後的回調問題。在你不知道的javascript一書中,對於回調的信任問題做了闡述當你使用第三方的庫的方法處理回調時很有可能遇到以下信任內容:怎麼解決???? 這種信任問題該怎麼辦?
  • async/await 原理及執行順序分析
    基於這個原因,ES7 引入了 async/await,這是 JavaScript 異步編程的一個重大改進,提供了在不阻塞主線程的情況下使用同步代碼實現異步訪問資源的能力,並且使得代碼邏輯更加清晰,而且還支持 try-catch 來捕獲異常,非常符合人的線性思維。所以,要研究一下如何實現 async/await。
  • Async:簡潔優雅的異步之道
    另外,這裡分開了每個異步操作,並規定好各自成功或失敗時傳遞出來的數據,近乎實際開發。1 登堂1.1 形式A函數也是函數,所以具有普通函數該有的性質。不過形式上有兩點不同:一是定義A函數時, function關鍵字前需要有 async關鍵字(意為異步),表示這是個A函數。二是在A函數內部可以使用 await關鍵字(意為等待),表示會將其後面跟隨的結果當成異步操作並等待其完成。