[JS] promise知識點與應用場景

来源:https://www.cnblogs.com/feixianxing/p/18286001/javascript-promise
-Advertisement-
Play Games

本文探討了JavaScript中Promise的基礎用法和各種靜態方法的應用場景。從解決非同步編程中的回調地獄問題,到鏈式調用、併發請求控制,再到最新的Promise.allSettled和Promise.any的應用。每種方法均通過代碼示例和詳細的應用場景進行了展示。 ...


Promise是JS中用於處理非同步操作的方法,- 支持鏈式調用從而解決了地獄回調問題。

Promise的基礎用法

狀態

promise有三種狀態:

  • Pending(待定):初始狀態,既不是成功也不是失敗。
  • Fulfilled(已成功):操作成功完成。
  • Rejected(已失敗):操作失敗。
const promise = new Promise((resolve, reject) => {
  // 非同步操作
  if (成功) {
    resolve(value);
  } else {
    reject(error);
  }
});

實例方法

Promise有三個實例方法,分別是thencatch,和finally

  • then用於處理Promise成功的情況:
promise.then((value) => {
  console.log(value);
});
  • catch用於處理Promise失敗的情況,即異常捕獲:
promise.catch((error) => {
  console.error(error);
});
  • finally:無論Promise最終狀態如何(成功或失敗),都會執行finally中的回調。
promise.finally(() => {
  console.log('操作完成');
});

鏈式調用

then方法可以返回一個Promise,併在後續鏈式地繼續調用then方法。

doSomething()
  .then((result) => {
    return doSomethingElse(result);
  })
  .then((newResult) => {
    return doThirdThing(newResult);
  })
  .then((finalResult) => {
    console.log(`Final result: ${finalResult}`);
  })
  .catch((error) => {
    console.error(error);
  });

鏈式調用只需要在尾部調用一次catch,在鏈式調用的過程中發生的異常都會被這個尾部的catch捕獲。

靜態方法

  • Promise.resolve(value):返回一個成功的Promise,值為value;常見於後面跟上then方法將一個函數推入微任務隊列;
  • Promise.reject(reason):返回一個失敗的Promise,原因為reason
  • Promise.all(iterable):並行執行多個Promise,所有Promise都成功時返回一個包含所有結果的新Promise,如果有任何一個失敗,則返回失敗的Promise。
Promise.all([promise1, promise2, promise3])
  .then((values) => console.log(values))
  .catch((error) => console.error(error));
  • Promise.race(iterable):返回第一個完成的Promise,無論成功還是失敗。
Promise.race([promise1, promise2, promise3])
  .then((value) => console.log(value))
  .catch((error) => console.error(error));

Promise.all的應用場景

併發請求,有時候在一個頁面中需要使用多個GET請求獲取頁面數據並渲染,並且這些GET請求沒有依賴關係,即不需要考慮請求順序。那麼這時就可以使用Promise.all併發執行這些GET請求。

const fetchUser = fetch('https://api.example.com/user');
const fetchPosts = fetch('https://api.example.com/posts');
const fetchComments = fetch('https://api.example.com/comments');

Promise.all([fetchUser, fetchPosts, fetchComments])
  .then(([userResponse, postsResponse, commentsResponse]) => {
    return Promise.all([userResponse.json(), postsResponse.json(), commentsResponse.json()]);
  })
  .then(([userData, postsData, commentsData]) => {
    console.log(userData, postsData, commentsData);
  })
  .catch((error) => {
    console.error('請求失敗', error);
  });

併發執行需要註意併發量不要太大,我們可以通過實現一個併發控制的類來限制併發量。

class RequestScheduler {
  constructor(concurrencyLimit) {
    this.concurrencyLimit = concurrencyLimit;
    this.running = 0;
    this.queue = [];
  }

  // 添加請求到隊列
  add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this.runNext();
    });
  }

  // 執行下一個請求
  runNext() {
    if (this.running >= this.concurrencyLimit || this.queue.length === 0) {
      return;
    }

    const { requestFn, resolve, reject } = this.queue.shift();
    this.running++;

    requestFn()
      .then((result) => {
        resolve(result);
      })
      .catch((error) => {
        reject(error);
      })
      .finally(() => {
        this.running--;
        this.runNext();
      });
  }
}

// 使用示例
const scheduler = new RequestScheduler(3); // 限制併發請求數量為3

const createRequest = (url) => () => fetch(url).then((response) => response.json());

const urls = [
  'https://jsonplaceholder.typicode.com/posts/1',
  'https://jsonplaceholder.typicode.com/posts/2',
  'https://jsonplaceholder.typicode.com/posts/3',
  'https://jsonplaceholder.typicode.com/posts/4',
  'https://jsonplaceholder.typicode.com/posts/5'
];

const requestPromises = urls.map((url) => scheduler.add(createRequest(url)));

Promise.all(requestPromises)
  .then((results) => {
    console.log('所有請求完成:', results);
  })
  .catch((error) => {
    console.error('請求失敗:', error);
  });
  • createRequest方法生成返回Promise的請求函數;
  • scheduler.add方法將一個請求添加到調度器中,併在併發限制允許的情況下執行;
  • Promise.all的作用是等待所有請求完成,並且統一處理異常。

Promise.race的應用場景

Promise.race方法關註的是最快出結果(不管是fulfilled還是rejected)的promise,可以實現超時處理。
超時處理:在race中傳入網路請求的promise和定時器的promise,如果網路請求在指定時間內到達則正常執行then流程,如果定時器先到達則表示超時,調用reject走catch流程。

const fetchWithTimeout = (url, timeout) => {
  const fetchPromise = fetch(url);
  const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('請求超時')), timeout));
  return Promise.race([fetchPromise, timeoutPromise]);
};

fetchWithTimeout('https://api.example.com/data', 5000)
  .then((response) => response.json())
  .then((data) => {
    console.log('請求成功', data);
  })
  .catch((error) => {
    console.error('請求失敗或超時', error);
  });

Promise.allSettled

Promise.allSettled 方法返回一個在所有給定的 Promise 已經 fulfilled 或 rejected 後的 Promise,並且帶有一個對象數組,每個對象表示對應的 Promise 結果。
如果是fulfilled,則結果欄位為value
如果是rejected,則結果欄位為reason

const promises = [
  Promise.resolve('resolved'),
  Promise.reject('rejected'),
  new Promise((resolve) => setTimeout(resolve, 1000, 'pending resolved'))
];

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

// 輸出:
// { status: 'fulfilled', value: 'resolved' }
// { status: 'rejected', reason: 'rejected' }
// { status: 'fulfilled', value: 'pending resolved' }

Promise.any

接受一個promise數組,返回一個promise。
和Promise.race不同,Promise.any會過濾掉所有rejected 的promise,而關註第一個fulfilled的promise的值。
如果數組中所有promise都被rejected的話,那麼會返回一個AggregateError類型的實例,帶有errors欄位,是一個數組,指明瞭每一個promise的reason
應用場景:any可以用來在多個備用資源中獲取最先成功響應的資源。
最快成功返回的備用資源:假設一個數據有多個可用來源,我們只需要拿到其中一個成功響應就可以了,那麼肯定是想要拿最快返回的那一個,這個時候用any就很nice~

const loadImage = (url) => new Promise((resolve, reject) => {
  const img = new Image();
  img.onload = () => resolve(url);
  img.onerror = () => reject(new Error(`Failed to load image at ${url}`));
  img.src = url;
});

const imageUrls = ['image1.png', 'image2.png', 'image3.png'];
const imagePromises = imageUrls.map(loadImage);

Promise.any(imagePromises)
  .then((result) => {
    console.log('第一個載入完成的圖片', result);
  })
  .catch((error) => {
    console.error('所有圖片載入失敗', error);
  });

Promise.withResolvers

這個方法返回一個新的promise對象和用於解決或拒絕它的resolvereject方法。
可以簡單地使用Promise手動實現:

Promise.withResolvers = function() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
};

使用 Promise.withResolvers() 關鍵的區別在於解決和拒絕函數現在與 Promise 本身處於同一作用域,而不是在執行器中被創建和一次性使用。
通常在一些重覆事件中使用,例如在處理流數據或者隊列的時候,在這些場景下通常可以減少嵌套,優化代碼結構。
這裡介紹MDN上面的案例:將流轉換為非同步可迭代對象。

// 定義 async generator 函數 readableToAsyncIterable,將流轉換為非同步可迭代對象
async function* readableToAsyncIterable(stream) {
  // 創建 Promise 和解析器對象
  let { promise, resolve, reject } = Promise.withResolvers();

  // 監聽流的錯誤事件,一旦出錯則調用 reject 方法
  stream.on("error", (error) => reject(error));

  // 監聽流的結束事件,一旦結束則調用 resolve 方法
  stream.on("end", () => resolve());

  // 監聽流的可讀事件,一旦流準備好可以讀取則調用 resolve 方法
  stream.on("readable", () => resolve());

  // 迴圈處理流中的數據塊,直到流不再可讀
  while (stream.readable) {
    // 等待當前的 Promise 解決
    await promise;

    let chunk;
    // 迴圈讀取流中的數據塊
    while ((chunk = stream.read())) {
      // 生成數據塊
      yield chunk;
    }

    // 獲取新的 Promise 和解析器對象,以便下一輪迴圈使用
    ({ promise, resolve, reject } = Promise.withResolvers());
  }
}

創建一個簡單的可讀流測試一下:

const { Readable } = require('stream');

// 測試函數
async function testReadableToAsyncIterable() {
  // 創建一個簡單的可讀流
  const data = ['Hello', 'World'];
  const readableStream = Readable.from(data);

  // 將可讀流轉換為非同步可迭代對象
  const asyncIterable = readableToAsyncIterable(readableStream);

  // 使用 for await...of 迴圈遍歷非同步可迭代對象中的數據塊,並驗證結果
  let result = '';
  for await (const chunk of asyncIterable) {
    result += chunk.toString();
  }

  // 斷言結果是否符合預期
  if (result === data.join('')) {
    console.log('測試通過:數據正常讀取和處理。');
  } else {
    console.error('測試失敗:數據讀取和處理出現問題。');
  }
}

// 執行測試函數
testReadableToAsyncIterable();

Promise規範與手寫Promise


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

-Advertisement-
Play Games
更多相關文章
  • ‍ 寫在開頭 點贊 + 收藏 學會 理解 forEach JavaScript 的forEach方法是一種流行的數組迭代工具。它為每個數組元素執行一次提供的函數。但是,與傳統的for 和 while迴圈不同,forEach它被設計為對每個元素執行該函數,沒有內置機制來提前停止或中 ...
  • 本文介紹了NodeJS中流(Stream)的概念、類型和應用。流通過將數據分成小塊進行處理,優化了記憶體使用和數據處理效率。文章涵蓋了四種基本流類型:可讀流、可寫流、雙工流和轉換流,並通過實例代碼演示瞭如何使用流進行高效的數據傳輸和處理。 ...
  • title: Nuxt框架中內置組件詳解及使用指南(三) date: 2024/7/8 updated: 2024/7/8 author: cmdragon excerpt: 摘要:“Nuxt 3框架中與組件的深度使用教程,包括如何使用這兩個組件進行頁面導航和載入指示的自定義配置與實戰示例。” ca ...
  • NodeJS是一個基於V8引擎和libuv的JavaScript運行時,適用於輕量級和高效的數據密集型Web應用。其單線程、非阻塞IO模型依賴事件迴圈和線程池管理非同步任務。使用NodeJS開發需避免阻塞主線程,正確處理事件和錯誤。 ...
  • zustand 和 jotai 是當下比較流行的react狀態管理庫。其都有著輕量、方便使用,和react hooks能夠很好的搭配,並且性能方面,對比React自身提供的context要好得多,因此被很多開發小伙伴所喜愛。 更有意思的是,這兩個庫的作者是同一個人,同時他還開源了另外一個狀態庫 va ...
  • 前言 vue2的時候想必大家有遇到需要在style模塊中訪問script模塊中的響應式變數,為此我們不得不使用css變數去實現。現在vue3已經內置了這個功能啦,可以在style中使用v-bind指令綁定script模塊中的響應式變數,這篇文章我們來講講vue是如何實現在style中使用script ...
  • title: Nuxt框架中內置組件詳解及使用指南(二) date: 2024/7/7 updated: 2024/7/7 author: cmdragon excerpt: 摘要:“本文詳細介紹了Nuxt 3中和組件的使用方法,包括組件的基本概念、屬性、自定義屬性、獲取引用以及完整示例,展示瞭如何 ...
  • title: Nuxt框架中內置組件詳解及使用指南(一) date: 2024/7/6 updated: 2024/7/6 author: cmdragon excerpt: 本文詳細介紹了Nuxt框架中的兩個內置組件和的使用方法與功能。確保包裹的內容僅在客戶端渲染,適用於處理瀏覽器特定功能或非同步數 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...