JS如何返回非同步調用的結果?

来源:https://www.cnblogs.com/sban/archive/2022/12/28/17009380.html
-Advertisement-
Play Games

這個問題作者認為是所有從後端轉向前端開發的程式員,都會遇到的第一問題。JS前端編程與後端編程最大的不同,就是它的非同步機制,同時這也是它的核心機制。 為了更好地說明如何返回非同步調用的結果,先看三個嘗試非同步調用的示例吧。 示例一:調用一個後端介面,返回介面返回的內容 function foo() { v ...


這個問題作者認為是所有從後端轉向前端開發的程式員,都會遇到的第一問題。JS前端編程與後端編程最大的不同,就是它的非同步機制,同時這也是它的核心機制。

為了更好地說明如何返回非同步調用的結果,先看三個嘗試非同步調用的示例吧。

示例一:調用一個後端介面,返回介面返回的內容

function foo() {
  var result
  $.ajax({
    url: "...",
    success: function(response) {
      result = response
    }
  });
  return result // 返回:undefined
}

函數foo嘗試調用一個介面並返回其內容,但每次執行都只會返回undefiend。

示例二:使用Promise的then方法,同樣是調用介面然後返回內容

function foo() {
  var result
  fetch(url).then(function(response) {
    result = response
  })
  return result // 返回:undefined
}

與上一個示例的調用一樣,也只會返回undefined。

示例三:讀取本地文件,然後返回其內容

function foo() {
  var result
  fs.readFile("path/to/file", function(err, response) {
    result = response
  })
  return result // 返回:undefined
}

毫無意外這個示例的調用結果也是undefined。

為什麼?

因為這三個示例涉及的三個操作————ajax、fetch、readFile都是非同步操作,從操作指令發出,到拿到結果,這中間有一個時間間隔。無論你的機器性能多麼強勁,這個間隔也無法完全抹掉。這是由JS的主線程是單線程而決定的,JS代碼執行到一定位置的時候,它不能等待,等待意味著用戶界面的卡頓,這是用戶不能容忍的。JS採用非同步線程優化該場景,當主線程中有非同步操作發起時,主線程不會阻塞,會繼續向下執行;當非同步操作有數據返回時,非同步線程會主動通知主線程:“Hi,老大,數據來了,現在要用嗎?”

“好的!馬上給我。”

這樣非同步線程把非同步代碼推給主線程,非同步代碼才得以執行。對於上面三個示例而言,result = response就是它們的非同步代碼。

下麵作者畫一張輔助理解這種機制吧:

image.png

當非同步線程準備好數據的時候,主線程也不是馬上就能處理,只有當主線程有空閑了,並且前面沒有排隊等待處理的數據了,新的非同步數據才能得以處理。

在瞭解了JS的非同步機制以後,下麵看前面三個示例如何正確改寫。

回調函數:最古老的非同步結果返回方式

先看示例一,使用回調函數改寫:

function foo(callback) {
  $.ajax({
    url: "...",
    success: function(response) {
      callback(response)
    }
  });
  // return result // 返回:undefined
}

在調用函數foo的時候,事先傳遞進來一個callback,當ajax操作取到介面數據的時候,將數據傳遞給callback,由callback自行處理。

這種基於回調的解決方案,雖然“巧妙”地解決了問題,但在存在多層非同步回調的複雜項目中,往往由於一個操作依賴於多個非同步數據而造成“回調噩夢”。

ES2015:使用Promise對象與then方法鏈式調用

第二種改進的方案,不使用回調函數,而是使用ES2015中新增的Promise及其then方法,下麵以示例二進行改造:

function foo() {
  return new Promise(function(resolve, reject) {
    fetch(url).then(function(response) {
        resolve(response)
    })
  })
}
foo().then(function(res){
  console.log(res)
})..catch(function(err) {
  //
})

foo返回一個Promise對象,註意,Promise僅是一個可能承載正確數據的容器,它並不是數據。在使用它的,需要調用它的then方法才能取得數據(在有數據返回的時候)。與then同時存在的另一個有用的方法是catch,它用於捕捉非同步操作可能出現的異常,處理可能的錯誤對加強魯棒性至關重要,這個catch方法不容忽視。

註意:示例中的fetch方法作者沒有給出具體實現,它在這裡是作為一個返回Promise對象的非同步操作被對待的,也因此我們看到了,在這個方法被調用後返回的對象上,也可以緊跟著調用then方法(第3行)。

但是,這種使用Promise的解決方案就完美了嗎,就沒有問題了嗎?顯然不是的。

ES2017:使用async/await語法關鍵字

過多的“緊隨”風格的then方法調用及catch方法調用,讓代碼的前後邏輯不清晰;當我們閱讀這樣的代碼時,並不是從上向下瀑布式閱讀的,而是時而上、時而下跳動著閱讀的,這很不舒服。不僅閱讀時不舒服,編寫時也很難以用一種像後端編程那樣的從上向下的簡潔的邏輯組織代碼。

下麵開始開始使用ES2017標準中提供async/await語法關鍵字,對示例三進行改寫:

function foo() {
  return new Promise(function(resolve, reject) {
    fs.readFile("path/to/file", function(err, response) {
        resolve(response)
    })
  })
}
(async function(){
  const res = await foo().catch(console.log)
  console.log(res)
})()

基於async/await語法關鍵字的方案,是使用Promise的方案的升級版,在這個方案中也使用了Promise。第8行第11行,這是一個IIFE(立即調用函數表達式),之所以要用一個只使用一次的臨時匿名函數將第9行第10行的代碼包裹起來,是因為await必須用在一個被async關鍵字修飾的函數或方法中,只能直接用到頂層的文件作用域或模塊作用域下。

使用這種方案的優化是,代碼可以像後端編程那樣從上向下寫,結構可以很清晰。這也是一種被稱為“非同步轉同步”的JS編程範式,在前端開發中已被普遍接受。

註意,“非同步轉同步”並沒有真正改變非同步代碼,非同步代碼仍然是非同步代碼,它們仍然會在非同步線程中先默默地執行,等有數據返回了再通知主線程處理。當我們使用這種編程模式的時候,一定不要在主線程上去await一個Promise,可以發起非同步操作,讓非同步操作像葡萄一樣掛在主線程上,但不能等待它們返回了再往下執行。

jQuery的Deferred Object(延遲對象)

先看一段Promise+then方法風格的jQuery代碼:

$.ajax({
  url: "test.html",
  context: document.body
}).done(function() {
  $(this).addClass("done")
});

第4行,這裡的done方法是jQuery自行實現的,$.ajax方法返回的是一個DeferredObject(延遲對象),這個對象上有done方法,這個方法與Promise的then類似。

jQuery成名在前,在ES2015標準誕生之前,jQuery的DeferredObject就已經被定義了。Promise本身並沒有神奇的地方,它可以發揮作用,主要依賴的是在JS中,Object是引用對象,繼承於Object原型的Promise也是引用對象,當非同步操作發起時,只有一個“空”的Promise被創建了,但是它的引用被保持了;當數據回來的時候,數據再被“裝填”進這個對象,這樣通過先前持有的引用,非同步代碼便可以訪問到對象上攜帶的數據。

Promise的勝利,更多是編程思想上的勝利,Promise的成功,也是編程思想上的成功。所有一種語言中編程思想上的成功,在其他語言中都可以被學習和借鑒。事實上在後端編程中,這種偽裝成同步代碼風格的非同步編程思想也極其普遍,它們擁有一個共同的名字,叫協程。

小結

在JS中處理非同步調用的結果,最佳實踐就是“非同步轉同步”:使用Promise + async/await語法關鍵字。在這裡async總是與await成對出現,一個async函數總是返回一個Promise,一個await關鍵字總是在嘗試“解開”一個Promise,結局要麼等到有價值的數據,要麼非同步出現非同步,什麼也沒有等到。為了避免出現異常,影響主線程的正常運行,一般要用catch規避異常。


著作權歸LIYI所有 基於CC BY-SA 4.0協議 原文鏈接:https://yishulun.com/posts/2022/33.html


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

-Advertisement-
Play Games
更多相關文章
  • 交叉編譯esp8089 編譯環境: 硬體:全志R528 ubuntu:Linux ubuntu 4.15.0-194-generic #205-Ubuntu SMP Fri Sep 16 19:49:27 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux 內核:tina ...
  • 摘要:不知道大家在生活中有沒有見過一些非常酷炫的可視化大屏應用? 本文分享自華為雲社區《老闆要的物聯網可視化大屏,我30分鐘就搞定了》,作者:華為雲社區精選 。 不知道大家在生活中有沒有見過一些非常酷炫的可視化大屏應用? 隨著數字化經濟的發展,我們對數據的呈現形式要求也越來越高,很多老闆動不動就讓我 ...
  • 摘要:為了持續打造核心競爭力,英克康健聯合華為雲,基於雲資料庫RDS for PostgreSQL全新打造了一個高性能、大容量、高可用的SaaS醫葯管理系統,助力萬千藥企業務邁上新臺階。 乘借數字化東風,醫葯行業呈現出一片欣欣向榮之景。作為一家高新技術企業,北京英克康健科技有限公司(簡稱“英克康健” ...
  • 熟悉Taier的小伙伴們應該都知道,在11月7日發佈的Taier1.3新版本中,我們融合了「DataSourceX 模塊」。這是十分重要的一個變化,移除Taier外部插件依賴,新增數據源插件相關特性,支持後續Taier對接更多的RDBMS類型的SQL任務。 本篇文章,就帶大家詳細瞭解一下DataSo ...
  • 作者:劉鄧忠 Mysql 是大家最常用的資料庫,下麵為大家帶來 mysql 索引下推知識點的分享,以便鞏固 mysql 基礎知識,如有錯誤,還請各位大佬們指正。 1 什麼是索引下推 索引下推 (Index Condition Pushdown,索引條件下推,簡稱 ICP),是 MySQL5.6 版本 ...
  • 好家伙,好家伙,本篇為《JS高級程式設計》第八章“對象、類與面向對象編程”學習筆記 1.原型鏈 原型鏈是JS實現"繼承"的方案之一 ECMA-262把原型鏈定義為ECMAScript的主要繼承方式。其基本思想就是通過原型繼承多個引用類型的屬性和方法。 重溫一下構造函數、原型和實例的關係:每個構造函數 ...
  • 一.概述 傳統許可權管理: 類似於這樣,每新增一個人都要重新給她一些許可權,是針對每個人單獨設置的,這種方法已經不適用於高效管控許可權的 基於此,RBAC許可權模型就誕生了,Role-Based Access control也就是基於角色的許可權控制,相對於傳統模式,這套方案強調一個==role角色== RB ...
  • 標簽 tag 基礎標簽 div 塊元素 介紹:沒有任何含義,主要用於 div 進行模塊佈局 類型:塊級元素 block,盒子占用寬度為一整行 屬性:沒有屬性 <div>我是模塊</div> 我是模塊 span 行內文本元素 介紹:沒有任何含義,主要用於展示文本內容 類型:內聯元素 inline,盒子 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...