[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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...