Promise的链式调用案例讲解

发布时间:2024年01月24日


这篇文章通过一个小案例,一起了解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)功能的操作流程。

  1. 初始化 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”。使用了可选链(?.),意味着如果 vcvc.valuenullundefined,则不会调用 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 链通常是最受欢迎的选择,因为它们提供了清晰和简洁的异步代码结构。

文章来源:https://blog.csdn.net/qq_32594913/article/details/135820657
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。