这篇文章通过一个小案例,一起了解Promise的链式调用
案例分析
这里我模拟某个语音功能的操作流程,每隔一秒钟,我去模拟一个操作,先看下面代码:
const chain = Promise.resolve(); chain .then(() => { // 1.模拟开始说话 打开vc vc状态speaking vc.value?.changeState("SPEAKING"); }) .then(() => { // 2. 语音输入 setTimeout(() => { question.value = "你好"; }, 1000); }) .then(() => { // 2. 继续输入 setTimeout(() => { question.value = "你好,欢迎使用vc"; }, 1000); });
问题解读
有没有发现上面代码有什么不妥的地方吗?
这段代码是一个使用 Promise 链式调用的示例,主要用于模拟某个语音控制(VC)功能的操作流程。
- 初始化 Promise 链
const chain = Promise.resolve();
这里,Promise.resolve()
创建了一个立即解析(fulfilled)的 Promise,它返回一个 Promise 对象,这个对象的状态已经被设置为解析(fulfilled),所以任何连接到这个 Promise 的 .then()
方法都会立即执行。
2. 第一个 .then()
.then(() => { // 1.模拟开始说话 打开vc vc状态speaking vc.value?.changeState("SPEAKING"); })
当 Promise 解析后,第一个 .then()
会被执行。这里,它模拟了开始说话的动作,并尝试更改 vc
(可能是一个代表语音控制的对象)的状态为 “SPEAKING”。使用了可选链(?.
),意味着如果 vc
或 vc.value
为 null
或 undefined
,则不会调用 changeState
方法,也不会报错。
3. 第二个 .then()
.then(() => { // 2. 语音输入 setTimeout(() => { question.value = "你好"; }, 1000); })
第二个 .then()
模拟了语音输入的过程。它使用 setTimeout
来模拟一个延迟(这里是1秒),之后更改 question.value
的值为 “你好”。这可以理解为在1秒后,模拟用户通过语音输入了“你好”。
但这里有一个问题:由于 setTimeout
是异步的,而 .then()
的回调函数是立即返回的,所以下一个 .then()
可能会在 setTimeout
的回调执行之前就被调用。
4. 第三个 .then()
.then(() => { // 3. 继续输入 setTimeout(() => { question.value = "你好,欢迎使用vc"; }, 1000); })
第三个 .then()
的逻辑与第二个相似,但它在 question.value
中添加了更多的文本。但这里又有一个同样的问题:它可能在上一个 setTimeout
执行完之前就运行了。
修改建议
- 连续的
.then()
调用之间并没有真正的依赖关系,它们只是简单地串联在一起。但由于setTimeout
的使用,它们之间的执行顺序可能并不是你预期的。
- 如果你希望每一步都在前一步的
setTimeout
之后执行,你应该将每一步都放在一个返回 Promise 的函数中,并在该 Promise 解析后再进行下一步。
修正后的代码可能类似于这样(使用了 async/await
和自定义的延迟函数):
async function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function simulateVC() { // 1.模拟开始说话 打开vc vc状态speaking vc.value?.changeState("SPEAKING"); await delay(1000); // 2. 语音输入 question.value = "你好"; await delay(1000); // 3. 继续输入 question.value = "你好,欢迎使用vc"; } simulateVC();
这样,每一步都会在前一步之后准确地执行。
还有什么其他方式实现这种延迟调用链的吗?
除了使用 async/await
和 Promise 之外,还有其他几种方式可以实现延迟调用链。以下是其中的一些方法:
1. 回调函数(Callback)
使用回调函数是最基本的方法之一,但它可能会导致所谓的“回调地狱”(Callback Hell),特别是当你有多个依赖的异步操作时。
function delayedLog(msg, delay, callback) { setTimeout(() => { console.log(msg); callback && callback(); }, delay); } delayedLog('Hello', 1000, () => { delayedLog('World', 1000); });
2. Promise 链
你已经展示了 Promise 链的方式,但是为了更清晰地表达每一步的延迟,我们可以稍微调整一下代码结构。
function delayedPromise(msg, delay) { return new Promise(resolve => { setTimeout(() => { console.log(msg); resolve(); }, delay); }); } delayedPromise('Hello', 1000) .then(() => delayedPromise('World', 1000));
3. Promise.all 和数组映射
如果你有一个需要依次延迟执行的任务数组,你可以使用 Promise.all
结合数组的 map
方法。不过要注意,Promise.all
会等待所有 Promise 都解决,但如果你想要依次执行它们,你应该使用 reduce
。
const tasks = ['Hello', 'World']; const delays = [1000, 1000]; tasks.reduce((chain, task, index) => { return chain .then(() => new Promise(resolve => { setTimeout(() => { console.log(task); resolve(); }, delays[index]); })); }, Promise.resolve());
4. Generator 函数和 co 库
在 ES6 之前,Generator 函数和第三方库如 co
被用来以更同步的方式编写异步代码
const co = require('co'); // 需要安装 co 库:npm install co function* generatorFunction() { yield delayedLog('Hello', 1000); yield delayedLog('World', 1000); } function delayedLog(msg, delay) { return new Promise(resolve => { setTimeout(() => { console.log(msg); resolve(); }, delay); }); } co(generatorFunction());
5. Observable(如 RxJS)
使用 Observable 也是一种处理异步数据流的方法,特别是在处理多个、复杂的异步事件时。RxJS 是一个流行的库,它提供了 Observable 的实现。
const { of } = require('rxjs'); const { delay, concatMap } = require('rxjs/operators'); of('Hello') .pipe( delay(1000), concatMap(x => of(x).pipe(delay(0))), // 仅仅是为了展示 concatMap 的使用 concatMap(x => of(`${x}, World`).pipe(delay(1000))) ) .subscribe(console.log);
在这个例子中,concatMap
被用来确保每个操作都在前一个操作完成后才执行。RxJS 提供了丰富的操作符来处理各种异步场景。
6. Async/Await 和 for…of 循环
如果你有一个异步操作的数组,并且想要依次执行它们,你可以使用 for...of
循环与 async/await
结合。
async function sequentialExecution(tasks) { for (const task of tasks) { await task(); } } const tasks = [ () => delayedLog('Hello', 1000), () => delayedLog('World', 1000) ]; sequentialExecution(tasks);
在这个例子中,sequentialExecution
函数会依次等待每个异步任务完成。tasks
数组包含了返回 Promise 的函数。
选择哪种方法取决于你的具体需求和你对每种技术的熟悉程度。在现代 JavaScript 开发中,async/await
和 Promise 链通常是最受欢迎的选择,因为它们提供了清晰和简洁的异步代码结构。