各位老铁,大家好!我是你们的老朋友,今天咱不聊风花雪月,来点硬核的——Promise.any
和Promise.allSettled
,看看这哥俩在并发控制里能整出啥新花样。
开场白:并发控制,你我的痛
话说,咱们写代码,尤其是涉及到网络请求、异步操作的时候,并发控制绝对是个绕不开的坎儿。搞不好,辛辛苦苦写的代码,就成了并发的牺牲品,bug 满天飞,用户体验稀烂。
以前,我们控制并发,要么自己手写各种复杂的逻辑,要么用一些现成的库,比如 async.js,但总觉得差点意思,不够优雅,不够现代。
现在不一样了,ES2020 带来了 Promise.any
和 Promise.allSettled
,这两个家伙,简直是并发控制领域的两员猛将,能让我们轻松搞定各种并发场景。
第一回合:Promise.any
——只要你行,我就行!
Promise.any
就像一个“选秀节目”,给它一堆 Promise,只要其中一个成功了,它就成功了!如果所有的 Promise 都失败了,它才会失败,并抛出一个 AggregateError
错误,告诉你“没一个行的!”
语法:
Promise.any(iterable);
iterable
: 一个可迭代对象,通常是一个 Promise 数组。
例子:
假设我们要从三个不同的服务器获取数据,但只要有一个服务器能成功返回数据,我们就认为成功了。
const fetchFromServer1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟服务器1成功返回数据
resolve("Data from Server 1");
}, 500);
});
};
const fetchFromServer2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟服务器2失败
reject("Server 2 failed");
}, 800);
});
};
const fetchFromServer3 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟服务器3成功返回数据
resolve("Data from Server 3");
}, 1200);
});
};
Promise.any([fetchFromServer1(), fetchFromServer2(), fetchFromServer3()])
.then(result => {
console.log("Successfully fetched data:", result); // 输出: Successfully fetched data: Data from Server 1
})
.catch(error => {
console.error("All servers failed:", error);
});
在这个例子中,fetchFromServer1
最先成功,所以 Promise.any
会立即resolve,并返回 Data from Server 1
。即使 fetchFromServer2
失败了,fetchFromServer3
后来也成功了,都不会影响 Promise.any
的结果。
并发控制中的应用:
-
竞速请求 (Race Condition): 当我们需要从多个来源获取相同的数据时,可以使用
Promise.any
,只要有一个来源返回了数据,就立即使用,而忽略其他的请求。 这可以显著提高响应速度。const getDataFromCache = () => { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟从缓存读取数据 const cachedData = localStorage.getItem("myData"); if (cachedData) { resolve(JSON.parse(cachedData)); } else { reject("No data in cache"); } }, 100); }); }; const getDataFromServer = () => { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟从服务器获取数据 const data = { name: "John Doe", age: 30 }; localStorage.setItem("myData", JSON.stringify(data)); // 模拟缓存数据 resolve(data); }, 500); }); }; Promise.any([getDataFromCache(), getDataFromServer()]) .then(data => { console.log("Data:", data); // 先从缓存获取数据,如果没有,再从服务器获取 }) .catch(error => { console.error("Failed to get data:", error); });
-
故障转移 (Failover): 当我们需要从多个服务器中选择一个可用的服务器时,可以使用
Promise.any
。如果一个服务器宕机了,Promise.any
会自动尝试其他的服务器,直到找到一个可用的服务器。const serverUrls = ["https://server1.example.com", "https://server2.example.com", "https://server3.example.com"]; const fetchDataFromServer = url => { return fetch(url) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .catch(error => { console.warn(`Failed to fetch from ${url}: ${error}`); return Promise.reject(error); // 关键: 必须 reject,才能让 Promise.any 尝试下一个 Promise }); }; const fetchFromAnyServer = () => { const promises = serverUrls.map(url => fetchDataFromServer(url)); return Promise.any(promises); }; fetchFromAnyServer() .then(data => { console.log("Data from available server:", data); }) .catch(error => { console.error("All servers failed:", error); });
注意: 在
fetchDataFromServer
中,如果fetch
失败了,必须reject
这个 Promise,才能让Promise.any
继续尝试其他的 Promise。 如果只是console.warn
然后resolve
一个undefined
,Promise.any
会认为这个 Promise 成功了,从而导致错误的结果。
第二回合:Promise.allSettled
——一个都不能少!
Promise.allSettled
就像一个“尽职尽责的监工”,给它一堆 Promise,它会等待所有的 Promise 都完成(不管是成功还是失败),然后返回一个结果数组,告诉你每个 Promise 的状态。
语法:
Promise.allSettled(iterable);
iterable
: 一个可迭代对象,通常是一个 Promise 数组。
例子:
假设我们要同时执行三个任务,但我们需要知道每个任务的执行结果,不管是成功还是失败。
const task1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Task 1 completed");
}, 300);
});
};
const task2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("Task 2 failed");
}, 500);
});
};
const task3 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Task 3 completed");
}, 700);
});
};
Promise.allSettled([task1(), task2(), task3()])
.then(results => {
console.log("All tasks completed:", results);
// 输出:
// All tasks completed: [
// { status: 'fulfilled', value: 'Task 1 completed' },
// { status: 'rejected', reason: 'Task 2 failed' },
// { status: 'fulfilled', value: 'Task 3 completed' }
// ]
});
在这个例子中,Promise.allSettled
会等待 task1
、task2
和 task3
都完成,然后返回一个数组,数组中的每个元素都是一个对象,包含 status
(fulfilled 或 rejected) 和 value
(如果成功) 或 reason
(如果失败)。
并发控制中的应用:
-
日志记录 (Logging): 当我们需要记录多个操作的执行结果时,可以使用
Promise.allSettled
。 即使某个操作失败了,我们仍然可以记录下失败的原因,方便排查问题。const logActivity = (activity, success) => { return new Promise((resolve, reject) => { setTimeout(() => { if (success) { console.log(`Logged: ${activity} - Success`); resolve(`Logged: ${activity} - Success`); } else { console.error(`Logged: ${activity} - Failed`); reject(`Logged: ${activity} - Failed`); } }, 200); }); }; const performTask = (taskName, shouldSucceed) => { return new Promise((resolve, reject) => { setTimeout(() => { if (shouldSucceed) { console.log(`${taskName} completed successfully`); resolve(`${taskName} completed successfully`); } else { console.error(`${taskName} failed`); reject(`${taskName} failed`); } }, 500); }); }; const tasks = [ performTask("Task A", true).then(() => logActivity("Task A", true)).catch(() => logActivity("Task A", false)), performTask("Task B", false).then(() => logActivity("Task B", true)).catch(() => logActivity("Task B", false)), performTask("Task C", true).then(() => logActivity("Task C", true)).catch(() => logActivity("Task C", false)) ]; Promise.allSettled(tasks) .then(results => { console.log("All tasks logged:", results); });
在这个例子中,即使
performTask("Task B", false)
失败了,我们仍然可以记录下失败的信息。 -
数据同步 (Data Synchronization): 当我们需要将数据同步到多个服务器时,可以使用
Promise.allSettled
。 我们可以知道哪些服务器同步成功了,哪些服务器同步失败了,然后根据情况进行重试或者其他处理。const syncDataToServer = (serverId, data) => { return new Promise((resolve, reject) => { setTimeout(() => { // 模拟数据同步 const success = Math.random() > 0.2; // 模拟 80% 的成功率 if (success) { console.log(`Data synchronized to server ${serverId}`); resolve(`Data synchronized to server ${serverId}`); } else { console.error(`Failed to synchronize data to server ${serverId}`); reject(`Failed to synchronize data to server ${serverId}`); } }, 400); }); }; const serverIds = ["Server A", "Server B", "Server C"]; const data = { message: "Hello, world!" }; const syncPromises = serverIds.map(serverId => syncDataToServer(serverId, data)); Promise.allSettled(syncPromises) .then(results => { console.log("Data synchronization results:", results); const failedServers = results .filter(result => result.status === "rejected") .map(result => result.reason); if (failedServers.length > 0) { console.warn("Failed to synchronize data to the following servers:", failedServers); // 可以根据情况进行重试或者其他处理 } });
在这个例子中,我们可以知道哪些服务器同步成功了,哪些服务器同步失败了,然后可以根据失败的服务器列表进行重试或其他处理。
第三回合:Promise.any
vs Promise.allSettled
——谁更胜一筹?
特性 | Promise.any |
Promise.allSettled |
---|---|---|
成功条件 | 只要有一个 Promise 成功,就立即成功。 | 必须等待所有的 Promise 都完成(不管是成功还是失败)。 |
失败条件 | 所有的 Promise 都失败,才会失败。 | 永远不会失败,总是会返回一个结果数组。 |
返回值 | 返回第一个成功的 Promise 的值。 | 返回一个数组,数组中的每个元素都是一个对象,包含 status (fulfilled 或 rejected) 和 value (如果成功) 或 reason (如果失败)。 |
使用场景 | 竞速请求、故障转移等,只需要一个 Promise 成功即可的场景。 | 日志记录、数据同步等,需要知道所有 Promise 的执行结果的场景。 |
并发控制关注点 | 注重速度,尽快得到一个可用的结果。 | 注重完整性,确保所有的操作都完成了,并记录下结果。 |
总结:
Promise.any
和 Promise.allSettled
是 ES2020 带来的两个强大的并发控制工具。Promise.any
适用于只需要一个 Promise 成功的场景,例如竞速请求和故障转移;Promise.allSettled
适用于需要知道所有 Promise 的执行结果的场景,例如日志记录和数据同步。
掌握了这两个工具,你的并发控制能力将会大大提升,写出更健壮、更高效的代码。
终极奥义:组合拳
实际上,Promise.any
和 Promise.allSettled
还可以组合使用,创造出更强大的并发控制模式。 比如,先使用 Promise.allSettled
获取所有任务的结果,然后根据结果决定是否使用 Promise.any
重新尝试失败的任务。
这就像是,你先派出所有的侦察兵去侦查,然后根据侦察结果,决定是否需要派出特种部队进行突击。
const attemptTask = (taskName, attemptNumber) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.3; // 模拟 70% 的成功率
if (success) {
console.log(`${taskName} - Attempt ${attemptNumber} completed successfully`);
resolve(`${taskName} - Attempt ${attemptNumber} completed successfully`);
} else {
console.error(`${taskName} - Attempt ${attemptNumber} failed`);
reject(`${taskName} - Attempt ${attemptNumber} failed`);
}
}, 300);
});
};
const tasks = [
attemptTask("Task X", 1),
attemptTask("Task Y", 1),
attemptTask("Task Z", 1)
];
Promise.allSettled(tasks)
.then(results => {
console.log("Initial task results:", results);
const failedTasks = results
.filter(result => result.status === "rejected")
.map((result, index) => ({ taskName: tasks[index].toString().match(/attemptTask("(.+)",/)[1], index })); //extract task name from the function string
if (failedTasks.length > 0) {
console.warn("Retrying failed tasks:", failedTasks.map(t => t.taskName));
const retryPromises = failedTasks.map(task => attemptTask(task.taskName, 2)); // retry once
return Promise.any(retryPromises); // Retry only the failed tasks
} else {
console.log("All tasks completed successfully on the first attempt.");
return Promise.resolve(); // All tasks succeeded, so resolve immediately
}
})
.then(retryResult => {
if (retryResult) {
console.log("At least one retry succeeded:", retryResult);
} else {
console.warn("All retries failed.");
}
})
.catch(error => {
console.error("An error occurred:", error);
});
在这个例子中,我们先使用 Promise.allSettled
执行所有的任务,然后找出失败的任务,并使用 Promise.any
重新尝试这些失败的任务。 如果所有的任务都第一次就成功了,则不需要重试。
总结的总结:
Promise.any
和 Promise.allSettled
是并发控制的利器,但它们并不是万能的。 在使用它们的时候,需要根据具体的场景进行选择,并注意一些细节问题,才能发挥它们最大的威力。
希望今天的分享对大家有所帮助! 记住,代码的世界,充满挑战,也充满乐趣! 祝大家编码愉快!