[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
  • 通過WPF的按鈕、文本輸入框實現了一個簡單的SpinBox數字輸入用戶組件並可以通過數據綁定數值和步長。本文中介紹了通過Xaml代碼實現自定義組件的佈局,依賴屬性的定義和使用等知識點。 ...
  • 以前,我看到一個朋友在對一個系統做初始化的時候,通過一組魔幻般的按鍵,調出來一個隱藏的系統設置界面,這個界面在常規的菜單或者工具欄是看不到的,因為它是一個後臺設置的關鍵界面,不公開,同時避免常規用戶的誤操作,它是作為一個超級管理員的入口功能,這個是很不錯的思路。其實Winform做這樣的處理也是很容... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式每次關閉時就會自動崩潰,一直找不到原因讓我幫忙看一下怎麼回事,這位朋友應該是第二次找我了,分析了下 dump 還是挺經典的,拿出來給大家分享一下吧。 二:WinDbg 分析 1. 為什麼會崩潰 找崩潰原因比較簡單,用 !analyze -v 命 ...
  • 在一些報表模塊中,需要我們根據用戶操作的名稱,來動態根據人員姓名,更新報表的簽名圖片,也就是電子手寫簽名效果,本篇隨筆介紹一下使用FastReport報表動態更新人員簽名圖片。 ...
  • 最新內容優先發佈於個人博客:小虎技術分享站,隨後逐步搬運到博客園。 創作不易,如果覺得有用請在Github上為博主點亮一顆小星星吧! 博主開始學習編程於11年前,年少時還只會使用cin 和cout ,給單片機點點燈。那時候,類似async/await 和future/promise 模型的認知還不是 ...
  • 之前在阿裡雲ECS 99元/年的活動實例上搭建了一個測試用的MINIO服務,以前都是直接當基礎設施來使用的,這次準備自己學一下S3相容API相關的對象存儲開發,因此有了這個小工具。目前僅包含上傳功能,後續計劃開發一個類似圖床的對象存儲應用。 ...
  • 目錄簡介快速入門安裝 NuGet 包實體類User資料庫類DbFactory增刪改查InsertSelectUpdateDelete總結 簡介 NPoco 是 PetaPoco 的一個分支,具有一些額外的功能,截至現在 github 星數 839。NPoco 中文資料沒多少,我是被博客園群友推薦的, ...
  • 前言 前面使用 Admin.Core 的代碼生成器生成了通用代碼生成器的基礎模塊 分組,模板,項目,項目模型,項目欄位的基礎功能,本篇繼續完善,實現最核心的模板生成功能,並提供生成預覽及代碼文件壓縮下載 準備 首先清楚幾個模塊的關係,如何使用,簡單畫一個流程圖 前面完成了基礎的模板組,模板管理,項目 ...
  • 假設需要實現一個圖標和文本結合的按鈕 ,普通做法是 直接重寫該按鈕的模板; 如果想作為通用的呢? 兩種做法: 附加屬性 自定義控制項 推薦使用附加屬性的形式 第一種:附加屬性 創建Button的附加屬性 ButtonExtensions 1 public static class ButtonExte ...
  • 在C#中,委托是一種引用類型的數據類型,允許我們封裝方法的引用。通過使用委托,我們可以將方法作為參數傳遞給其他方法,或者將多個方法組合在一起,從而實現更靈活的編程模式。委托類似於函數指針,但提供了類型安全和垃圾回收等現代語言特性。 基本概念 定義委托 定義委托需要指定它所代表的方法的原型,包括返回類 ...