JavaScript的非同步編程之Promise

来源:https://www.cnblogs.com/fengbaba/archive/2022/10/28/16837866.html
-Advertisement-
Play Games

Promise 一種更優的非同步編程統一 方法,如果直接使用傳統的回調函數去完成複雜操作就會形成回調深淵 // 回調深淵 $.get('/url1'() => { $.get('/url2'() => { $.get('/url3'() => { $.get('/url4'() => { $.get( ...


Promise

一種更優的非同步編程統一 方法,如果直接使用傳統的回調函數去完成複雜操作就會形成回調深淵

// 回調深淵
$.get('/url1'() => {
  $.get('/url2'() => {
    $.get('/url3'() => {
      $.get('/url4'() => {
        $.get('/url5'() => {
          // 大概就是這樣子的
        })
      })
    })
  })
})

CommonJS 社區提出了 Promise 規範,在ES2015中被標準化,成為語言規範。當等待狀態改編程成功或者失敗之後就再也不能再被改變了,成功的時候觸發onFulfilled 回調,失敗的時候觸發onRejected 回調

image-20221028232857794

Promise 簡單使用

new Promise 傳入一個回調函數,這個回調函數兩個參數,第一個把Promise 改成為成功的狀態,第二個參數把Promise改變成失敗的狀態,捕獲成功和異常可以使用.then.catch方法,這兩個方法返回的也是一個Promise對象

// 演示
const promsie = new Promise((resolve, reject) => {
  reject(1)
})

promsie.then((value) => {
  console.log(value)
}, (err) => {
  // end 執行完之後才會執行這個
  console.log(err)
})

// end 會先執行
console.log('end')

不管Promise中有沒有非同步操作,then方法中的回調函數依然會進入回調隊列中排隊,會等同步代碼執行完之後才會執行

Promise寫一個請求函數

function ajax (url) {
  return new Promise((resove, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    // 新方法可以直接接受一個j對象
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resove(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

ajax('/json1.json').then(ret => {
  console.log(ret)
}).catch(err => {
  console.log(err)
})

如果需要多個連續的請求可以使用鏈式調用

ajax('/json1.json').then(ret => {
  return ajax('/json2.json')
}).then(ret => {
  return ajax('/json3.json')
}).then(ret => {
  return ajax('/json4.json')
})

這種鏈式調用是不是很熟悉,在jqeury中也有鏈式調用,jquery中是返回了本身這個對象所以可以實現鏈式調用,那麼在Promise中是不是這樣呢

 let promsie1 = ajax('/json1.json')
    
 let promise2 = promsie1.then(ret => {
   console.log(ret)
 }).catch(err => {
   console.log(err)
 })

 console.log(promsie1 === promise2) // false

let a  = $("body").attr('class', 'body')
let b = a.prop('disabled', true)
console.log(a === b) // true

經過測試發現,Promise返回的是一個全新的Promise對象,返回全新的Promise對象的目的就是為了實現Promise的鏈條,每個.then方法負責不同的任務,互不幹擾,如果不斷的鏈式調用then方法,這裡的每個then方法都在為上一個then方法返回的Promise對象去添加狀態明確後的回調,這些Promise會依次執行,而且我們可以在then方法中去手動返回一個Promise回調。如果then方法中的回調函數返回了值,則會給下一個then方法的回調函數傳遞這個返回的值,如果沒有返回那麼預設返回的就是undefined總結一下就是

  • Promise對象的then方法會返回一個全新的Promise對象
  • 後面的then方法就是在為上一個then返回的Promise註冊回調
  • 前面的then方法中的回調函數的返回值回作為後面then方法回調的參數
  • 如果回調中返回的是Promise, 那後面的then方法的回調會等待他的結束

捕獲異常

onRejected 回調會在Promise執行異常或者拋出的異常時觸發, 捕獲異常有兩種方式,第一種, then(成功處理的回調函數, 異常處理的回調函數)then方法中傳遞兩個回調函數,第二種用.catch 方法去捕獲異常,catch方法其實就是then方法的別名,相當於then方法第一個參數傳undefined

// then(成功處理的回調函數, 異常處理的回調函數)
ajax('/json1.json').then(ret => {
 	console.log(err)
}, err => {
  console.log(err)
})


// catch
ajax('/json1.json').then(ret => {
 	console.log(err)
}).catch(err => {
  console.log(err)
})
// catch
ajax('/json1.json').then(ret => {
 	console.log(err)
}).then(undefined,err => {
  console.log(err)
})

這兩種方式還是有很大的差異,catch 其實是在給上一個then返回的Promise 捕獲異常,但是如果是同一個鏈條下的Promise的錯誤會向下傳遞直到有catch方法捕獲,而then方法傳遞兩個回調函數的捕獲異常的方式只會捕獲誰上一個Promise的錯誤

ajax('/json1.json').then(ret => {
  console.log(ret)
}).then(undefined, err => {
  console.log(err)
}).then(ret => {
  console.log(ret)
}).then(ret => {
  console.log(ret)
})

// catch 捕獲異常
ajax('/json1.json').then(ret => {
  console.log(ret)
}).catch(err => {
  // 這裡能捕獲之前的所有Promise的異常
})

// 傳遞then 第二個參數捕獲異常
ajax('/json1.json').then(ret => {
  console.log(ret)
}).then(undefined, err => {
  console.log(err)
  throw new Error('故意的異常')
}, (err) => {
  // 這裡能捕獲故意的錯誤
}).then(ret => {
  console.log(ret)
}).then(ret => {
  console.log(ret)
}).catch(err => {
  // 這個時候已經捕獲不到異常了,因為上一個故意的異常已經被捕獲了,根據then方法會返回一個Promise所以捕獲異常之後會返回一個成功的Promise
})

還可以全局捕獲異常, 這種全局方式捕獲異常是不推薦使用的,應該在代碼塊中明確的去捕獲對應的異常

// 瀏覽器環境中
window.addEventListener('unhandledrejection', event => {
  console.log(event.reason, event.promise)
 	// reason 失敗原因,
  // promise 失敗的Promise
  event.preventDefault()
}, false)

// nodejs中 
process.on('unhandledRejection', (reason, promise) => {
  console.log(reason, promise)
  // reason 失敗原因,
  // promise 失敗的Promise
})

如果需要無論成功和錯誤都需要執行則可以用finally來實現

ajax('/json1.json')
  .then(ret => {
    console.log('成功執行這個')
  }).catch(err => {
    console.log("失敗執行這個")
  })
  .finally(function() {
    console.log("成功和失敗都會執行這個")
});

Promise 靜態方法

Promise.resolve

快速的一個值轉化為一個Promise 對象, 這種方式和 new Promise 返回一個值是等價的

Promise.resolve({
  data: "hahah"
})

new Promise((resolve) => {
  resolve({
    data: "hahah"
  })
})

如果傳入的是一個Promise對象會原封不動的把這個對象返回

function ajax (url) {
  return new Promise((resove, reject) => {
    var xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    // 新方法可以直接接受一個j對象
    xhr.responseType = 'json'
    xhr.onload = function () {
      if (this.status === 200) {
        resove(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    xhr.send()
  })
}

let promise1 = ajax('/url')

let promise2 = Promise.resolve(promise1)

console.log(promise1 === promise2) // true

如果傳入的是一個對象,並且這個對象也有一個跟Promise一樣的then方法,也就是說這個方也也可以接收到onFulfilled, onRejected 兩個回調,並且可以調用回調傳遞參數,這種有then方法的對象實現了一個thenable的介面,支持這種對象的原因是因為原生Promise還沒有被普及之前,很多時候都是第三方的庫實現的Promise

Promise.resolve({
  then (onFulfilled, onRejected) {
    onFulfilled('123')
  }
}).then(ret => {
  console.log(ret) // 123
})

Promise.reject

快速創建一個一定是失敗的Promise對象,這個方法的參數就是Promise失敗的原因

Promise.reject("嘿嘿,這就是錯誤的理由").catch(err => {
  console.log(err) // 嘿嘿,這就是錯誤的理由
})

Promise.all

接收一個數組,這些元素都是一個Promise對象,這個方法會返回一個全新的Promise對象,當內部所有Promise的都完成之後Promise.all返回的Promise對象才會完成。這個時候Promise.all返回的Promise對象拿到的結果是一個數組,這個數組中包含了每一個Promise返回的結果。值得註意的是只有數組中的所有Promise都成功了結束了,Promise.all返回的Promise對象才會成功結束。如果數組中有一個Promise失敗的結束了,那麼Promise.all返回的Promise對象也會以失敗的結束

Promise.all([
  ajax('/url1'),
  ajax('/url2'),
  ajax('/url3'),
  ajax('/url4'),
]).then(values => {
  console.log(values)
}).catch(err => {
  console.log(err)
})

Promise.race

Promise.all方法一樣也是接收一個數組,這些元素都是一個Promise對象,這個方法會返回一個全新的Promise對象,但是與Promise.all方法不同的是Promise.all是等待所有任務的結束而結束, Promise.race只會等待第一個結束的任務而結束

const request = ajax('/api/???')
const timeout = new Promise((resolve, reject) => {
  setTimeout(() =>  reject('timeout'), 5000);
})

Promise.race([
  request,
  timeout
]).then(ret => {
  console.log(ret)
}).catch(err => {
  console.log(err)
})

上面代碼中,如果介面在5秒之前介面返回了,那麼我們可以正常的得到返回結果,如果5秒還沒有返回,那麼請求就沒有辦法把結果返回回來了,因為timeout這個Promise會在5秒後以失敗的方式結束,而Promise.race就是以第一個結束的Promise而結束

Promise.allSettled

Promise.all、Promise.race方法一樣也是接收一個數組,這些元素都是一個Promise對象,這個方法會返回一個全新的Promise對象,與他們不同的是無論這些Promise執行是成功還是失敗都是等這些Promise都完成了之後才會完成,當有多個彼此不依賴的非同步任務成功完成時,或者總是想知道每個promise的結果時,通常使用它

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));

// > "fulfilled"
// > "rejected"

Promise.any

Promise.race方法一樣也是接收一個數組,這些元素都是一個Promise對象,這個方法會返回一個全新的Promise對象,不同的是只要有一個Promise執行是成功的就算成功,只有全部都失敗了才會失敗。這個全新的PromiseonFulfilled的回調函數的參數為第一個成功完成的Promise所傳遞的數據

const alwaysError = new Promise((resolve, reject) => {
  reject("失敗就失敗下一個成功");
});

const two = new Promise((resolve, reject) => {
  setTimeout(resolve, 30, "我是第二個完成的Promise");
});

const three = new Promise((resolve, reject) => {
  setTimeout(resolve, 70, "我是第三個個完成的Promise");
});

const one = new Promise((resolve, reject) => {
  setTimeout(resolve, 10, "我是最先完成的Promise");
});

Promise.any([two, three, alwaysError, one]).then((value) => {
  console.log(value); // 我是最先完成的Promise
  // 這個value是最先完成的Promise傳遞的值也就是=>我是最先完成的Promise
})

Promise 執行時序問題

巨集任務,微任務

測試執行順序

console.log('global start')

Promise.resolve().then(ret => {
  console.log('promise')
})

console.log('global end')
// outlog
// 1. global start
// 2. global end
// 3. promise

鏈式調用多個執行看執行順序

console.log('global start')

Promise.resolve().then(ret => {
  console.log('promise1')
}).then(ret => {
  console.log('promise2')
}).then(ret => {
  console.log('promise3')
})

console.log('global end')

// outlog
// 1. global start
// 2. global end
// 3. promise1
// 4. promise2
// 5. promise3

加入setTimeout

console.log('global start')

setTimeout(() => {
  console.log('settimeout')
}, 0);

Promise.resolve().then(ret => {
  console.log('promise1')
}).then(ret => {
  console.log('promise2')
}).then(ret => {
  console.log('promise3')
})

console.log('global end')
// 1. global start
// 2. global end
// 3. promise1
// 4. promise2
// 5. promise3
// 6. settimeout

沒想到吧,Promise的非同步時序執行優點特殊。舉個例子、假如我們去銀行ATM辦理存款,辦完之後突然想起要轉一筆賬,這時候肯定會直接辦理轉賬業務,不會到後面重新排隊再轉賬。這個例子中我們排隊就像在javascipt中的等待執行的任務一樣,我們隊伍中的每一個人都對應著回調回列中的一個任務、。回調隊列中任務稱之為巨集任務,而巨集任務執行過程中可以臨時加上一些額外需求,這些額外的需求可以選擇作為一個新的巨集任務進行到隊列中排隊。上面的setTimeout就會作為巨集任務再次到回調隊列中排隊,也可以跟我們剛的例子一樣作為當前任務的微任務直接在當前任務結束之後立即執行。Promise的回調會作為微任務執行,會在本輪調用的末尾去執行,所以說上面代碼會先列印promise1,promise2,promise3在列印settimeout

微任務是在後來才被引入到js中的,他的目的是為了提高整體的響應能力,目前的絕大多數非同步調用都是作為巨集任務執行。Promise、MutationObservernodejs 中的process.nextTick會作為微任務在本輪調用的末尾執行

更多內容微信公眾號搜索充饑的泡飯
小程式搜一搜開水泡飯的博客


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

-Advertisement-
Play Games
更多相關文章
  • 更多技術文章,請關註我的個人博客 www.immaxfang.com 和小公眾號 Max的學習札記。 Redis 事務簡介 Redis 只是提供了簡單的事務功能。其本質是一組命令的集合,事務支持一次執行多個命令,在事務執行過程中,會順序執行隊列中的命令,其他客戶端提交的命令請求不會插入到本事務執行命 ...
  • 閱識風雲是華為雲信息大咖,擅長將複雜信息多元化呈現,其出品的一張圖(雲圖說)、深入淺出的博文(雲小課)或短視頻(雲視廳)總有一款能讓您快速上手華為雲。更多精彩內容請單擊此處。 摘要:Hudi是數據湖的文件組織層,對Parquet格式文件進行管理提供數據湖能力,支持多種計算引擎。 本文分享自華為雲社區 ...
  • 摘要:通常在運維監控出現CPU使用率較高、P80/P95指標較高、慢SQL數量上升等現象,或者業務出現超時報錯時,優先應排查是否出現慢SQL。 本文分享自華為雲社區《GaussDB慢SQL常見定位處理手段》,作者:酷哥。 關鍵指標 通常在運維監控出現CPU使用率較高、P80/P95指標較高、慢SQL ...
  • 雲時代已經來臨,雲上很多場景下都需要數據的遷移、備份和流轉,各大雲廠商也大都提供了自己的遷移工具。本文主要介紹京東雲資料庫為解決用戶數據遷移的常見場景所提供的解決方案。 ...
  • 1. 詳解explain分析SQL; 2. 索引失效的幾個場景; 3. SQL優化的幾個場景: 4. 大批量插入; 5. order by; 6. group by; 7. limit分頁; 8. insert操作; 9. 嵌套查詢; 10. or條件 ...
  • 一、前言 作為全鏈路數字化技術與服務提供商,袋鼠雲提供了從數據湖、大數據基礎平臺、離線開發、實時開發、數據服務、數據治理、指標管理、客戶數據洞察、數據孿生可視化等全產品體系的服務。 圍繞著“行業應用”及“通用應用”,袋鼠雲聚焦數智提供全維數字解決方案,幫助企業實現降本增效、快捷轉型,迄今為止袋鼠雲已 ...
  • uni-app預設使用uni-ui全端相容的、高性能UI框架,在我們開發過程中可以滿足大部分的需求了,並且如果是為了相容性,還是強烈建議使用uni-ui作為UI框架使用。 如果作為初創公司,自身又不想費太多精力去自己設計一套UI框架,那麼DCloud插件市場提供了很多優秀的UI框架,最重要的是有些U ...
  • 近年來,AR不斷發展,作為一種增強現實技術,給用戶帶來了虛擬和現實世界的融合體驗。但用戶已經不滿足於單純地將某件虛擬物品放在現實場景中來感受AR技術,更想用身體姿勢來觸發某個指令,達到更具真實感的人機交互功能。 比如在AR體感游戲中,用戶不必點擊按鍵進行頻繁操作,通過某個姿勢即可觸發;在拍攝短視頻時 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...