JavaScript中如何从外部解决Promise,特别是在需要等待一个异步操作完成后再执行另一个操作的场景
这是JavaScript中那些在现实世界中极其强大的技巧之一,适用于处理异步操作的各种场景,尤其是需要在外部控制Promise的解决或拒绝时。以下是一个典型的应用场景和解决方案。
强大的实际应用场景:动作(A)等待另一个动作(B)
假设我们有两个异步操作,用户在执行动作(A)时需要等待动作(B)完成。举个例子,一个社交应用中,用户可以创建、保存和发布帖子(类似于Medium)。如果用户希望在帖子保存后发布,该如何处理?
HTML结构
<p>
Save status:
<b><span id="save-status">Not saved</span></b>
</p>
<p>
Publish status:
<b><span id="publish-status">Not published</span></b>
</p>
<button id="save">Save</button>
<button id="publish">Publish</button>
问题:用户想在帖子保存时发布
如果用户点击发布按钮时,帖子还没有保存,应该等待帖子保存完毕后再发布。这需要确保在发布之前,帖子已经保存。
JavaScript 代码
saveButton.onclick = () => {
save();
};
publishButton.onclick = async () => {
await publish();
};
let saveResolve;
let hasSaved = false;
async function save() {
hasSaved = false;
saveStatus.textContent = 'Saving...';
// ✅ 从外部解决Promise
await makeSaveRequest();
saveResolve();
hasSaved = true;
saveStatus.textContent = 'Saved';
}
async function waitForSave() {
if (!hasSaved) {
await new Promise((resolve) => {
saveResolve = resolve;
});
}
}
async function publish() {
publishStatus.textContent = 'Waiting for save...';
await waitForSave();
publishStatus.textContent = 'Published';
}
解释:
saveResolve
是一个用于在save()
完成后手动解决的Promise
。waitForSave()
函数在保存未完成时,使用新的Promise
等待保存完成。- 一旦
save()
完成,外部saveResolve()
将被调用,Promise 解决,发布操作继续。
使用 Deferred
类简化代码
为了解决外部Promise的逻辑,我们可以抽象出一个 Deferred
类,它可以更好地管理Promise的解决和拒绝。
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
const deferred = new Deferred();
// 在外部解决
deferred.resolve();
重构后的代码
const deferredSave = new Deferred();
let hasSaved = false;
async function save() {
hasSaved = false;
saveStatus.textContent = 'Saving...';
// ? 从外部解决Promise
await makeSaveRequest();
deferredSave.resolve();
hasSaved = true;
saveStatus.textContent = 'Saved';
}
async function waitForSave() {
if (!hasSaved) await deferredSave.promise;
}
async function publish() {
publishStatus.textContent = 'Waiting for save...';
await waitForSave();
publishStatus.textContent = 'Published';
}
使用 Deferred
类使代码更简洁,同时保留了相同的功能。Deferred
类可以很好地管理Promise,避免了手动保存 resolve
或 reject
的麻烦。
将事件流转换为Promise
在某些场景下,可能需要将事件流转换为Promise。这时可以利用 Deferred
,实现如下:
// data-fetcher.js
const deferred = new Deferred();
let dataDeferred;
function startListening() {
dataDeferred = new Deferred();
eventStream.on('data', (data) => {
dataDeferred.resolve(data);
});
}
async function getData() {
return await dataDeferred.promise;
}
// client.js
const { startListening, getData } = require('./data-fetcher.js');
startListening();
// ? 等待事件发生后返回
const data = await getData();
解释:
- 在
data-fetcher.js
中,我们通过startListening()
开始监听事件流。 - 当事件流触发
data
时,调用dataDeferred.resolve(data)
解决Promise。 - 客户端代码通过
await getData()
等待事件数据的返回。
最后的思考
从外部解决Promise是一种非常强大的技术,能够让你的代码在处理异步事件和用户操作时更加灵活和清晰。通过使用 Deferred
类或类似的库(如 ts-deferred
),可以更轻松地管理异步流程。