[技術翻譯]在現代JavaScript中編寫非同步任務

来源:https://www.cnblogs.com/yzsunlei/archive/2020/01/13/12189960.html
-Advertisement-
Play Games

本周再來翻譯一些技術文章,本次預計翻譯三篇文章如下: "04.[譯]使用Nuxt生成靜態網站" ( "Generate Static Websites with Nuxt" ) "05.[譯]Web網頁內容是如何影響電池功耗的" ( "How Web Content Can Affect Power ...


本周再來翻譯一些技術文章,本次預計翻譯三篇文章如下:

我翻譯的技術文章都放在一個github倉庫中,如果覺得有用請點擊star收藏。我為什麼要創建這個git倉庫?目的是通過翻譯國外的web相關的技術文章來學習和跟進web發展的新思想和新技術。git倉庫地址:https://github.com/yzsunlei/javascript-article-translate

在本文中,我們將探討過去圍繞非同步執行的JavaScript的演變以及它如何改變我們編寫和讀取代碼的方式。我們將從Web開發的開始,一直到現代非同步模式示例。
JavaScript作為編程語言具有兩個主要特征,這兩個特征對於理解我們的代碼是如何工作的都很重要。首先是它的同步特性,這意味著代碼將幾乎在您閱讀時逐行運行,其次,它是單線程的,任何時候都只執行一個命令。

隨著語言的發展,新的模塊出現在場景中以允許非同步執行。開發人員在解決更複雜的演算法和數據流時嘗試了不同的方法,從而導致圍繞它們的新介面和模式的出現。

同步執行和觀察者模式

如引言中所述,JavaScript通常會逐行運行您編寫的代碼。即使在最初的幾年中,該語言也有例外,儘管它們很少,您可能已經知道它們:HTTP請求,DOM事件和時間間隔。

const button = document.querySelector('button');

// observe for user interaction
button.addEventListener('click', function(e) {
  console.log('user click just happened!');
})

如果添加事件偵聽器(例如,單擊元素並觸發用戶交互),則JavaScript引擎會將事件偵聽器回調的任務放入隊列,但將繼續執行其當前堆棧中的內容。完成那裡的調用之後,它現在將運行偵聽器的回調。

此行為類似於網路請求和計時器發生的情況,它們是Web開發人員訪問非同步執行的第一個模塊。

儘管這些是JavaScript中常見的同步執行例外的,但至關重要的是要瞭解該語言仍然是單線程的,並且儘管它可以將Task排隊,非同步運行它們然後返回主線程,但它只能一次執行一段代碼。

我們的工具手冊,其中Alla Kholmatova探索瞭如何創建有效且可維護的設計系統來設計出色的數字產品。認識Design Systems,瞭解常見的陷阱,陷阱和Alla多年來汲取的經驗教訓。

例如,讓我們發送一個網路請求。

var request = new XMLHttpRequest();
request.open('GET', '//some.api.at/server', true);

// observe for server response
request.onreadystatechange = function() {
  if (request.readyState === 4 && xhr.status === 200) {
    console.log(request.responseText);
  }
}

request.send();

伺服器返回時,分配給該方法的任務將onreadystatechange放入隊列(代碼在主線程中繼續執行)。

註意:解釋JavaScript引擎如何將任務排隊和處理執行線程是一個很複雜的主題,可能值得一讀。不過,我還是建議您查看“事件迴圈到底是什麼?”菲利普·羅伯茨(Phillip Roberts)提供的幫助,以幫助您更好地理解。

在上述每種情況下,我們都在響應外部事件。達到一定的時間間隔,用戶操作或伺服器響應。我們本身無法創建非同步任務,我們始終觀察到發生的事件超出了我們的範圍。

這就是為什麼將這種模板式的代碼稱為“觀察者模式”,addEventListener在這種情況下,可以更好地由介面表示。很快,暴露這種模式的事件庫或框架蓬勃發展。

NODE.JS和事件觸發器

一個很好的例子是Node.js,該頁面將自己描述為“非同步事件驅動的JavaScript運行時”,因此事件觸發器和回調是一等公民。它甚至用EventEmitter已經實現了一個構造函數。

const EventEmitter = require('events');
const emitter = new EventEmitter();

// respond to events
emitter.on('greeting', (message) => console.log(message));

// send events
emitter.emit('greeting', 'Hi there!');

這不僅是非同步執行的通用方法,而且是其生態系統的核心模式和慣例。Node.js開闢了一個在不同環境中甚至在網路之外編寫JavaScript的新時代。結果,其他非同步情況也是可能的,例如創建新目錄或寫入文件。

const { mkdir, writeFile } = require('fs');

const styles = 'body { background: #ffdead; }';

mkdir('./assets/', (error) => {
  if (!error) {
    writeFile('assets/main.css', styles, 'utf-8', (error) => {
      if (!error) console.log('stylesheet created');
    })
  }
})

您可能會註意到,回調error函數的第一個參數為,如果需要響應數據,則將其作為第二個參數。這被稱為“錯誤優先回調模式”,它成為作者和貢獻者為其自己的程式包和庫所採用的約定。

Promise和無盡的回調鏈

隨著Web開發麵臨更複雜的問題需要解決,對更好的非同步工件的需求出現了。如果我們查看最後一個代碼片段,我們會看到重覆的回調鏈,隨著任務數量的增加,回調鏈的擴展效果就很差。

例如,讓我們僅添加兩個步驟,即文件讀取和樣式預處理。

const { mkdir, writeFile, readFile } = require('fs');
const less = require('less')

readFile('./main.less', 'utf-8', (error, data) => {
  if (error) throw error
  less.render(data, (lessError, output) => {
    if (lessError) throw lessError
    mkdir('./assets/', (dirError) => {
      if (dirError) throw dirError
      writeFile('assets/main.css', output.css, 'utf-8', (writeError) => {
        if (writeError) throw writeError
        console.log('stylesheet created');
      })
    })
  })
})

我們可以看到,由於多個回調鏈和重覆的錯誤處理,隨著正在編寫的程式變得越來越複雜,代碼變得更加難以為人所知。

Promise,包裝和連鎖模式

Promises最初宣佈它們是JavaScript語言的新功能時,並沒有引起太多關註,它們並不是一個新概念,因為其他語言在幾十年前就已經實現了類似的功能。事實是,自從出現以來,他們發現我所做的大多數項目的語義和結構都發生了很大變化。

Promises不僅引入了供開發人員編寫非同步代碼的內置解決方案,而且還為Web開發(如Web規範)的新功能的構建基礎打開了Web開發的新階段fetch。

從回調方法遷移到基於Promise的方法在項目(例如庫和瀏覽器)中變得越來越普遍,甚至Node.js也開始緩慢地遷移到它們。

例如,包裝一下Node的readFile方法:

const { readFile } = require('fs');

const asyncReadFile = (path, options) => {
  return new Promise((resolve, reject) => {
    readFile(path, options, (error, data) => {
      if (error) reject(error);
      else resolve(data);
    })
  });
}

在這裡,我們通過在Promise構造函數中執行,resolve在方法結果成功時以及reject在定義錯誤對象時調用,來掩蓋回調。

當一個方法返回一個Promise對象時,我們可以通過將一個函數傳遞給來遵循其成功的解析then,其參數是Promise被解析的值,在這種情況下為data。

如果在方法期間引發錯誤catch,則將調用該函數(如果存在)。

註意:如果您需要更深入地瞭解Promises的工作方式,我建議Jake Archibald 在Google的Web開發博客上寫的“JavaScript Promises:Introduction”一文。

現在我們可以使用這些新方法並避免回調鏈。

asyncRead('./main.less', 'utf-8')
  .then(data => console.log('file content', data))
  .catch(error => console.error('something went wrong', error))

具有創建非同步任務的方法和清晰的界面以跟蹤其可能的結果,使該行業擺脫了觀察者模式。基於Promise的代碼似乎可以解決不可讀且容易出錯的代碼。

隨著更好的語法或更清晰的錯誤消息在編碼時突出顯示有所幫助,對於開發人員來說,更易於推理的代碼變得更具可預測性,並且執行路徑的情況更好,更容易捕捉可能的代碼陷阱。

Promises由於在社區中的普及程度很高,Node.js迅速發佈了其I/O方法的內置版本以返回Promise對象,例如從中導入文件操作fs.promises。

它甚至提供了一個promisify實用工具,用於包裝遵循錯誤優先回調模式的所有函數,並將其轉換為基於Promise的函數。

但是,Promises在所有情況下都能提供幫助嗎?

讓我們重新想象一下用Promises編寫的樣式預處理任務。

const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less')

readFile('./main.less', 'utf-8')
  .then(less.render)
  .then(result =>
    mkdir('./assets')
      .then(writeFile('assets/main.css', result.css, 'utf-8'))
  )
  .catch(error => console.error(error))

代碼中的冗餘明顯減少了,尤其是在我們現在所依賴的錯誤處理方面catch,但是Promises某種程度上未能提供與操作串聯直接相關的清晰代碼縮進。

這實際上是在調用then之後的第一個語句上實現的readFile。這些行之後發生的事情是需要創建一個新的作用域,我們可以在該作用域中首先創建目錄,然後將結果寫入文件中。這就導致了縮進節奏的中斷,乍看之下很難確定指令序列。

解決此問題的一種方法是預先處理該問題的自定義方法,並允許該方法正確連接,但是我們將向似乎已經具有實現任務所需功能的代碼引入更多的複雜性。

註意:這是一個示常式序,我們可以控制某些方法,它們都遵循行業慣例,但並非總是如此。通過更複雜的串聯或引入具有不同類型的庫,我們可以輕鬆破壞代碼風格。

令人高興的是,JavaScript社區再次從其他語言語法中學到了東西,並添加了一種表示法,可以在很多情況下幫助非同步任務串聯而不是像同步代碼那樣令人愉悅或直截了當。

async和await

A Promise在執行時被定義為一個未解析的值,創建a的實例Promise是對該模塊的顯式調用。

const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less')

readFile('./main.less', 'utf-8')
  .then(less.render)
  .then(result =>
    mkdir('./assets')
      .then(writeFile('assets/main.css', result.css, 'utf-8'))
  )
  .catch(error => console.error(error))

在async方法內部,我們可以使用await保留字來確定a的解析度,Promise然後繼續執行。

讓我們使用此語法重新訪問或編寫代碼段。

const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less')

async function processLess() {
  const content = await readFile('./main.less', 'utf-8')
  const result = await less.render(content)
  await mkdir('./assets')
  await writeFile('assets/main.css', result.css, 'utf-8')
}

processLess()

註意:請註意,由於我們今天不能在非同步函數的範圍之外使用,因此需要將所有代碼移至方法await。

每次async方法找到一條await語句時,它將停止執行,直到處理中的值或Promise被解析為止。

儘管非同步執行,但使用async/await表示法會有明顯的後果,代碼看起來好像是async,這是我們開發人員更習慣查看和推理的。

錯誤處理呢?為此,我們使用在該語言中已經存在很長時間的語句,try和catch。

const { mkdir, writeFile, readFile } = require('fs').promises;
const less = require('less');

async function processLess() {
  try {
    const content = await readFile('./main.less', 'utf-8')
    const result = await less.render(content)
    await mkdir('./assets')
    await writeFile('assets/main.css', result.css, 'utf-8')
  } catch(e) {
    console.error(e)
  }
}

processLess()

放心,在該過程中引發的任何錯誤將由該catch語句內的代碼處理。我們在中心位置負責錯誤處理,但是現在我們有了一個易於閱讀和遵循的代碼。

具有返回值的後續操作不需要存儲在mkdir不會破壞代碼節奏的變數中;也無需在以後的步驟中創建新的作用域來訪問result的值。

可以肯定地說,Promises是該語言中引入的一個基本模塊,對於在JavaScript中啟用async/await表示法是必需的,您可以在現代瀏覽器和最新版本的Node.js中使用它。

註意:最近在JSConf中,Node的創建者和第一貢獻者Ryan Dahl很遺憾沒有堅持Promises的早期開發,主要是因為Node的目標是創建事件驅動的伺服器和文件管理,而Observer模式更適合於此。

結論

將Promises引入Web開發世界的目的是改變我們在代碼中排隊操作的方式,並改變了我們對代碼執行進行推理的方式以及我們編寫庫和包的方式。

但是擺脫回調鏈很難解決,我認為then在多年習慣於觀察者模式和主要提供商採用的方法之後,不得不通過一種方法並不能幫助我們擺脫思路。像Node.js這樣的社區。

正如諾蘭·勞森(Nolan Lawson)在其有關Promise串聯中錯誤使用的出色文章中所說,舊的回調習慣會死掉!稍後,他解釋瞭如何避免這些陷阱。

我認為Promises是中間步驟,它允許以自然的方式生成非同步任務,但並沒有幫助我們進一步改進更好的代碼模式,有時您實際上需要更適應和改進的語言語法。

當我們嘗試使用JavaScript解決更複雜的難題時,我們看到了對更成熟語言的需求,並嘗試了以前不曾在網路上看到過的架構和模式。

我們仍然不知道ECMAScript規範的表現如何,因為我們一直將JavaScript治理擴展到網路之外,並嘗試解決更複雜的難題。

現在很難說我們需要從語言中真正地將這些難題轉變成更簡單的程式所需要的東西,但是我對Web和JavaScript本身如何推動事物,試圖適應挑戰和新環境感到滿意。與十年前開始在瀏覽器中編寫代碼相比,現在我覺得JavaScript是一個更加非同步的友好的地方。

原文鏈接:https://www.smashingmagazine.com/2019/10/asynchronous-tasks-modern-javascript/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #!/bin/bash echo "修改主機名" hostnamectl set-hostname wangxfa hostname sleep 1 echo "查看並關閉防火牆" systemctl status firewalld systemctl stop firewalld systemc ...
  • 屬性順序錯誤 一般情況下欄位類型要放在前面,限制參數放在後面,UNSIGNEDZEROFILL 之間沒有先後順序,主鍵 KEY 和 auto_increment 要放在UNSIGNED ZEROFILL 後面 否則報錯 。設置為主鍵的欄位沒有 deafault參數,not null 一般放在最後面。 ...
  • # 註釋內容 -- 註釋內容 -- 創建資料庫 king CREATE DATABASE king; -- 查看當前伺服器下有哪些資料庫 SHOW DATABASES; SHOW SCHEMAS; -- 查看king資料庫的詳細信息 SHOW CREATE DATABASE king; -- 創建數 ...
  • 提到Redis,大家肯定都聽過,並且應該都在項目中或多或少的使用過,也許你覺得Redis用起來挺簡單的呀,但如果有人問你下麵的幾個問題(比如同事或者面試官),你能回答的上來嗎? 1. 什麼是Redis? 2. Redis能存儲哪幾種數據結構? 3. Redis有幾種持久化機制?它們的優缺點分別是什麼 ...
  • 前提要述:參考書籍《MySQL必知必會》 2.1 MySQL簡介 2.1.1 什麼是MySQL MySQL是一種關係資料庫管理系統。負責資料庫中數據的存儲,檢索,管理和處理。 2.1.2 MySQL的優點 成本低——MySQL是開源的,一般可以免費使用。 性能——MySQL執行快。 可信賴,體積小。 ...
  • 個人博客 "http://www.milovetingting.cn" AOP之AspectJ的簡單使用 AOP的定義 AOP為Aspect Oriented Programming的縮寫,意為:面向切麵編程,通過預編譯方式和運行期間動態代理實現程式功能的統一維護的一種技術。 以上關於AOP的定義引 ...
  • 這是一個很長很長的story!-芝麻粒兒創作 開篇 源碼地址:GitHub 本文目的,將Unity集成到Android端,學完本文後你可以做到 Android任意佈局載入Unity 3D場景 任意操作佈局中的按鈕/3D物品(縮放旋轉等) 互相消息通信(你叼我,我叼你) 自由切換Unity中的場景 動 ...
  • 一、思路分析和效果圖 用vue來實現一個瀑布流效果,載入網路圖片,同時有下拉刷新和上拉載入更多功能效果。然後針對這幾個效果的實現,捋下思路: 先看個效果動圖: 靜態截圖: 二、具體實現步驟 2.1、頁面結構設計,測試數據準備。 本地準備一個json文件數據,放在項目public文件夾下。註意,本地測 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...