Node.js 異步任務(wù)協(xié)作方案:7 種實(shí)用方案與真實(shí)項(xiàng)目案例
多個(gè)獨(dú)立異步任務(wù),如何高效協(xié)作?從
Promise.all到隊(duì)列控制,本文用 7 個(gè)真實(shí)場(chǎng)景給出答案。
在 Node.js 開(kāi)發(fā)中,我們經(jīng)常需要同時(shí)處理多個(gè)獨(dú)立的異步任務(wù):讀取多個(gè)配置文件、調(diào)用多個(gè)外部接口、批量上傳文件……這些任務(wù)彼此獨(dú)立,但最終結(jié)果需要協(xié)同處理。如果只是簡(jiǎn)單地逐個(gè) await,性能會(huì)大打折扣;如果盲目并發(fā),又可能引發(fā)資源耗盡或錯(cuò)誤處理混亂。
本文將介紹 7 種成熟的異步協(xié)作方案,每種都配有真實(shí)項(xiàng)目中的代碼示例,幫助你快速應(yīng)用到實(shí)際工作中。
1.Promise.all—— 應(yīng)用啟動(dòng)加載必要配置
場(chǎng)景:服務(wù)啟動(dòng)時(shí)必須讀取數(shù)據(jù)庫(kù)、Redis 和第三方密鑰三個(gè)配置文件,任何一個(gè)缺失或格式錯(cuò)誤都不能繼續(xù)啟動(dòng)。
const fs = require('fs').promises;
async function loadConfigs() {
const [db, redis, secrets] = await Promise.all([
fs.readFile('./config/db.json', 'utf8').then(JSON.parse),
fs.readFile('./config/redis.json', 'utf8').then(JSON.parse),
fs.readFile('./config/secrets.json', 'utf8').then(JSON.parse)
]);
console.log('所有配置加載完成', { db, redis, secrets });
}特點(diǎn):全成功或全失敗,結(jié)果以數(shù)組順序返回。適合“缺一不可”的場(chǎng)景。
2.Promise.allSettled—— 批量同步用戶數(shù)據(jù)到多個(gè)外部系統(tǒng)
場(chǎng)景:用戶更新個(gè)人資料后,需要同步到 CRM、郵件服務(wù)、推送系統(tǒng)。允許個(gè)別失敗,但要記錄失敗原因,后續(xù)重試。
async function syncUserToExternal(user) {
const tasks = [
syncToCRM(user),
syncToEmailService(user),
syncToPushService(user)
];
const results = await Promise.allSettled(tasks);
const failed = results.filter(r => r.status === 'rejected');
if (failed.length) {
console.error(`同步失敗 ${failed.length} 個(gè)系統(tǒng)`, failed.map(f => f.reason));
// 將失敗記錄到數(shù)據(jù)庫(kù),等待重試隊(duì)列處理
}
return results;
}特點(diǎn):等待所有任務(wù)完成,無(wú)論成功或失敗,都能拿到每個(gè)任務(wù)的最終狀態(tài)。
3.Promise.race—— HTTP 請(qǐng)求超時(shí)控制
場(chǎng)景:調(diào)用外部 API,必須在 3 秒內(nèi)返回結(jié)果,否則自動(dòng)降級(jí)使用緩存數(shù)據(jù)。
function fetchWithTimeout(url, timeout = 3000) {
const controller = new AbortController();
const fetchPromise = fetch(url, { signal: controller.signal });
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => {
controller.abort();
reject(new Error('請(qǐng)求超時(shí)'));
}, timeout)
);
return Promise.race([fetchPromise, timeoutPromise]);
}
// 使用
try {
const data = await fetchWithTimeout('https://slow-api.example.com/data', 3000);
console.log(data);
} catch (err) {
console.log('使用緩存數(shù)據(jù)');
}特點(diǎn):只取最先完成的那個(gè)結(jié)果(成功或失?。?。常用于超時(shí)控制、多源競(jìng)速。
4.Promise.any—— 多 CDN 資源容災(zāi)加載
場(chǎng)景:前端靜態(tài)資源部署在三個(gè) CDN 上,只要任意一個(gè) CDN 返回成功,就使用該資源,忽略失敗的 CDN。
async function loadScriptFromCDNs(urls) {
const fetchTasks = urls.map(url => fetch(url).then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
}));
try {
const scriptContent = await Promise.any(fetchTasks);
eval(scriptContent); // 實(shí)際項(xiàng)目中建議使用更安全的方式
console.log('腳本加載成功');
} catch (aggregateError) {
console.error('所有 CDN 均不可用', aggregateError.errors);
}
}
loadScriptFromCDNs([
'https://cdn1.example.com/lib.js',
'https://cdn2.example.com/lib.js',
'https://cdn3.example.com/lib.js'
]);特點(diǎn):只要有一個(gè)成功就返回,全部失敗才拋出異常。非常適合冗余容災(zāi)設(shè)計(jì)。
5. 事件計(jì)數(shù)器 —— 舊式多文件寫(xiě)入完成后合并壓縮
場(chǎng)景:維護(hù)一個(gè)老項(xiàng)目(基于回調(diào)風(fēng)格),需要等三個(gè)日志文件全部寫(xiě)入磁盤(pán)后,再執(zhí)行合并壓縮操作。
const EventEmitter = require('events');
const fs = require('fs');
class FileWriter extends EventEmitter {
writeAndNotify(file, data) {
fs.writeFile(file, data, (err) => {
if (err) this.emit('error', err);
else this.emit('done', file);
});
}
}
// 應(yīng)用
const writer = new FileWriter();
let completed = 0;
const total = 3;
function onAllDone() {
console.log('所有文件寫(xiě)入完成,開(kāi)始合并壓縮');
// 執(zhí)行合并邏輯
}
writer.on('done', (file) => {
console.log(`${file} 寫(xiě)入完成`);
if (++completed === total) onAllDone();
});
writer.writeAndNotify('log1.txt', 'data1');
writer.writeAndNotify('log2.txt', 'data2');
writer.writeAndNotify('log3.txt', 'data3');特點(diǎn):原始但可控,適合無(wú)法使用 Promise 的舊環(huán)境或需要細(xì)粒度事件監(jiān)聽(tīng)時(shí)使用。
6. 流式處理 —— 實(shí)時(shí)聚合多個(gè)傳感器數(shù)據(jù)流
場(chǎng)景:物聯(lián)網(wǎng)網(wǎng)關(guān)接收溫度、濕度、氣壓三個(gè)傳感器的實(shí)時(shí)數(shù)據(jù)流,需要每收到一組(三個(gè)傳感器各一個(gè)值)就計(jì)算平均值并推送。
const { fromEvent, merge, bufferCount } = require('rxjs');
const { EventEmitter } = require('events');
const sensorA = new EventEmitter();
const sensorB = new EventEmitter();
const sensorC = new EventEmitter();
// 模擬每秒推送一次數(shù)據(jù)
setInterval(() => sensorA.emit('data', Math.random() * 30), 1000);
setInterval(() => sensorB.emit('data', Math.random() * 60), 1000);
setInterval(() => sensorC.emit('data', Math.random() * 10), 1000);
// 將 EventEmitter 轉(zhuǎn)為 Observable
const streamA = fromEvent(sensorA, 'data');
const streamB = fromEvent(sensorB, 'data');
const streamC = fromEvent(sensorC, 'data');
// 合并并每收到3個(gè)值(各一個(gè))計(jì)算一次平均值
merge(streamA, streamB, streamC)
.pipe(bufferCount(3))
.subscribe(values => {
const avg = values.reduce((a, b) => a + b, 0) / values.length;
console.log(`實(shí)時(shí)平均傳感器值: ${avg.toFixed(2)}`);
});特點(diǎn):適合結(jié)果逐步產(chǎn)生、需要實(shí)時(shí)響應(yīng)的場(chǎng)景。RxJS 提供了強(qiáng)大的組合能力。
7. 隊(duì)列控制并發(fā) —— 限制同時(shí)上傳文件的數(shù)量
場(chǎng)景:用戶一次選擇了 100 個(gè)文件上傳到云存儲(chǔ),必須控制同時(shí)上傳的并發(fā)數(shù)為 5,避免網(wǎng)絡(luò)擁塞和服務(wù)器壓力過(guò)大。
const pLimit = require('p-limit');
const fs = require('fs').promises;
const path = require('path');
async function uploadFile(filePath) {
console.log(`開(kāi)始上傳 ${path.basename(filePath)}`);
await new Promise(r => setTimeout(r, 1000)); // 模擬上傳
console.log(`完成上傳 ${path.basename(filePath)}`);
return filePath;
}
async function uploadAll(filePaths) {
const limit = pLimit(5); // 最多5個(gè)并發(fā)
const tasks = filePaths.map(filePath =>
limit(() => uploadFile(filePath))
);
const results = await Promise.all(tasks);
console.log(`全部上傳完成,共 ${results.length} 個(gè)文件`);
}
// 生成100個(gè)測(cè)試文件路徑
const files = Array.from({ length: 100 }, (_, i) => `/tmp/file${i}.txt`);
uploadAll(files);特點(diǎn):既保證并發(fā)效率,又避免資源耗盡。配合 Promise.all 可以等待所有任務(wù)完成。
總結(jié):一張表幫你快速選擇
| 場(chǎng)景 | 推薦方案 |
|---|---|
| 所有任務(wù)必須全部成功,結(jié)果一起使用 | Promise.all |
| 容忍部分失敗,但需要知道每個(gè)任務(wù)的狀態(tài) | Promise.allSettled |
| 只取最快結(jié)果(如超時(shí)、多源競(jìng)速) | Promise.race |
| 只要有一個(gè)成功即可,忽略失敗 | Promise.any |
| 舊項(xiàng)目回調(diào)風(fēng)格或需要細(xì)粒度控制 | 事件計(jì)數(shù)器 / EventEmitter |
| 結(jié)果流式輸出、復(fù)雜組合(如傳感器數(shù)據(jù)) | RxJS / 異步迭代器 |
| 大量任務(wù)且需控制并發(fā)數(shù)量 | 隊(duì)列 + p-limit |
在實(shí)際項(xiàng)目中,90% 的異步協(xié)作需求都可以用 Promise.all 和 Promise.allSettled 解決。對(duì)于更復(fù)雜的場(chǎng)景,再考慮流式處理或隊(duì)列控制。掌握這些模式,你的 Node.js 異步編程能力將更上一層樓。
到此這篇關(guān)于Node.js 異步任務(wù)協(xié)作方案:7 種實(shí)用方案與真實(shí)項(xiàng)目案例的文章就介紹到這了,更多相關(guān)node.js 異步任務(wù)協(xié)作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Nodejs?http模塊返回內(nèi)容中文亂碼問(wèn)題及解決
這篇文章主要介紹了Nodejs?http模塊返回內(nèi)容中文亂碼問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
使用Node.js調(diào)用DeepSeek大模型API的完整實(shí)戰(zhàn)教程
每個(gè)開(kāi)發(fā)者應(yīng)學(xué)會(huì)調(diào)用大模型API,如如OpenAI SDK已成為事實(shí)標(biāo)準(zhǔn),安全管理APIKey和理解process.env至關(guān)重要,本文從項(xiàng)目初始化Node.js項(xiàng)目到調(diào)用DeepSeekAPI,詳解異步控制與工程化開(kāi)發(fā)流程,需要的朋友可以參考下2026-06-06
Lua表達(dá)式和控制結(jié)構(gòu)學(xué)習(xí)筆記
這篇文章主要介紹了Lua表達(dá)式和控制結(jié)構(gòu)學(xué)習(xí)筆記,本文講解了算術(shù)操作符、關(guān)系操作符、邏輯操作符、局部變量與作用域、控制結(jié)構(gòu)等內(nèi)容,需要的朋友可以參考下2014-12-12
Node.js檢測(cè)端口(port)是否被占用的簡(jiǎn)單示例
大家有沒(méi)有遇到過(guò)在開(kāi)啟本地服務(wù)時(shí),有這么一種情況:當(dāng)前端口已經(jīng)被另一個(gè)項(xiàng)目使用了,導(dǎo)致服務(wù)開(kāi)啟失敗。那么接下來(lái),我們通過(guò)簡(jiǎn)簡(jiǎn)單單的示例代碼來(lái)檢測(cè)端口是否已經(jīng)被占用。有需要的朋友們可以參考借鑒。2016-09-09
express如何解決ajax跨域訪問(wèn)session失效問(wèn)題詳解
這篇文章主要給大家介紹了關(guān)于express如何解決ajax跨域訪問(wèn)session失效問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
Node.js中的HTTP請(qǐng)求與響應(yīng)詳解
本文詳細(xì)講解了Node.js中的HTTP請(qǐng)求與響應(yīng),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

