[JS] promise知識點與應用場景
Promise是JS中用于處理異步操作的方法,- 支持鏈式調用從而解決了地獄回調問題。
Promise的基礎用法
狀態
promise有三種狀態:
- Pending(待定):初始狀態,既不是成功也不是失敗。
- Fulfilled(已成功):操作成功完成。
- Rejected(已失敗):操作失敗。
const promise = new Promise((resolve, reject) => {
// 異步操作
if (成功) {
resolve(value);
} else {
reject(error);
}
});
實例方法
Promise有三個實例方法,分別是then,catch,和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對象和用于解決或拒絕它的resolve和reject方法。
可以簡單地使用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
?? Promises/A+ 規范
示例代碼:
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.state = PENDING; // 初始狀態為 pending
this.value = undefined; // 成功時的值
this.reason = undefined; // 失敗時的原因
this.onFulfilledCallbacks = []; // 存儲成功時的回調函數
this.onRejectedCallbacks = []; // 存儲失敗時的回調函數
// 定義 resolve 函數,用于將狀態轉變為 fulfilled,并執行成功的回調函數
const resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
// 執行所有成功回調函數
this._executeCallbacks(this.onFulfilledCallbacks, this.value);
}
};
// 定義 reject 函數,用于將狀態轉變為 rejected,并執行失敗的回調函數
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
// 執行所有失敗回調函數
this._executeCallbacks(this.onRejectedCallbacks, this.reason);
}
};
try {
// 執行執行器函數,并傳入 resolve 和 reject 函數
executor(resolve, reject);
} catch (error) {
// 如果執行器函數拋出異常,則直接 reject
reject(error);
}
}
// 定義 then 方法,用于鏈式調用
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// 定義處理成功的函數
const handleFulfilled = (value) => {
try {
// 如果 onFulfilled 存在,則執行它,并獲取結果
const result = onFulfilled ? onFulfilled(value) : value;
this._handleResult(result, resolve, reject);
} catch (error) {
reject(error);
}
};
// 定義處理失敗的函數
const handleRejected = (reason) => {
try {
// 如果 onRejected 存在,則執行它,并獲取結果
const result = onRejected ? onRejected(reason) : reason;
this._handleResult(result, resolve, reject);
} catch (error) {
reject(error);
}
};
// 根據當前 Promise 的狀態執行不同的邏輯
if (this.state === FULFILLED) {
// 使用 queueMicrotask 來模擬微任務,確保在當前事件循環結束后執行 handleFulfilled
queueMicrotask(() => handleFulfilled(this.value));
} else if (this.state === REJECTED) {
// 使用 queueMicrotask 來模擬微任務,確保在當前事件循環結束后執行 handleRejected
queueMicrotask(() => handleRejected(this.reason));
} else if (this.state === PENDING) {
// 如果當前狀態仍為 pending,則將處理成功和失敗的函數加入對應的回調數組中
this.onFulfilledCallbacks.push(value => {
queueMicrotask(() => handleFulfilled(value));
});
this.onRejectedCallbacks.push(reason => {
queueMicrotask(() => handleRejected(reason));
});
}
});
}
// 定義 catch 方法,用于捕獲 Promise 鏈中的錯誤
catch(onRejected) {
return this.then(null, onRejected);
}
// 靜態方法 resolve,返回一個立即 resolved 的 Promise 對象
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
// 靜態方法 reject,返回一個立即 rejected 的 Promise 對象
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
// 靜態方法 all,接收一個 Promise 數組,返回一個新的 Promise,當所有 Promise 都成功時才成功,結果為一個值數組
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
promises.forEach((promise, index) => {
promise.then((value) => {
results[index] = value;
count++;
// 當所有 Promise 都成功時,resolve 結果數組
if (count === promises.length) {
resolve(results);
}
}).catch(reject); // 一旦有 Promise 失敗,則整體 Promise 也失敗
});
});
}
// 靜態方法 race,接收一個 Promise 數組,返回一個新的 Promise,以最先 resolved 或 rejected 的 Promise 的結果作為結果
static race(promises) {
return new MyPromise((resolve, reject) => {
// 遍歷 Promise 數組,一旦有 Promise 解決或拒絕,則立即 resolve 或 reject
promises.forEach((promise) => {
promise.then(resolve).catch(reject);
});
});
}
// 私有方法,用于處理 then 方法返回的結果
_handleResult(result, resolve, reject) {
if (result instanceof MyPromise) {
// 如果返回結果是一個 Promise 實例,則繼續鏈式調用
result.then(resolve, reject);
} else {
// 否則直接將結果傳遞給下一個 Promise 的 resolve
resolve(result);
}
}
// 私有方法,用于執行回調函數數組中的所有回調
_executeCallbacks(callbacks, arg) {
callbacks.forEach(callback => {
// 使用 queueMicrotask 來模擬微任務,確保在當前事件循環結束后執行回調
queueMicrotask(() => callback(arg));
});
}
}
為什么使用數組存儲回調函數?
通常我們在使用promise的時候只會調用一次then方法并傳入一個回調函數,但其實then方法是可以多次調用的,例如下面這段代碼,則會添加多個回調,因此需要使用數組存儲回調函數。
const promise = new Promise((resolve, reject) => {
// 執行異步操作
setTimeout(() => {
resolve('成功'); // 改變狀態為 fulfilled
}, 1000);
});
promise.then(
value => console.log('成功處理1:', value),
reason => console.error('失敗處理1:', reason)
);
promise.then(
value => console.log('成功處理2:', value),
reason => console.error('失敗處理2:', reason)
);

浙公網安備 33010602011771號