【问题标题】:Why setTimeout performed parallel if they part of a when chain?如果 setTimeout 是 when 链的一部分,为什么它们会并行执行?
【发布时间】:2021-04-15 21:36:35
【问题描述】:

作为探索then 链的一部分,我遇到了两件我无法解释的事情。

这是代码:

let x1 = new Promise((resolve) => {
  setTimeout(() => {
    console.log('10 seconds first timeout');
    resolve(1);
  }, 10000)
});
let x2 = x1.then((p1) => {
  setTimeout(() => {
    console.log('10 seconds second timeout');
    console.log(p1);
    return ('2');
  }, 10000);
});
let x3 = x2.then((p2) => {
  setTimeout(() => {
    console.log('10 seconds third timeout');
    console.log(p2);
    return ('3');
  }, 10000);
});

console.log(x1, x2, x3);

问题是:

  1. x2.then 回调在 x1.then 回调打印之后立即打印。但是x2.then 会收到一个超时时间为 10 秒的回调,那么为什么不在打印前等待 10 秒呢?在第一个和第二个之间有 10 秒,正如预期的那样。

  2. x1.then 回调返回一个字符串'2',据我了解,这个值应该是返回的Promise 的值(由Promise 包装),那么为什么尝试打印到控制台console.log(p2)它打印 undefined 而不是字符串 '2'?

【问题讨论】:

  • 您提供给x1.thenx2.then 的函数并不是在10 秒内解决的承诺,它们只是设置了一个计时器以在10 秒内打印一些内容并立即完成。此外,.then() 的回调不会显式返回任何内容。 return 语句是给setTimeout 的回调的一部分。

标签: javascript asynchronous timeout


【解决方案1】:

x1 看起来没问题——你已经承诺了回调,所以 x2 在调用 resolve 之前不会触发。但是x2x3 不会做你想做的事,因为它们不会返回承诺。 setTimeout 回调中的 returns 与主线 Promise 链无关,它们只是从回调块返回到忽略返回值的范围内。

鉴于一般的x1 模式,您可以将promisified timer 放入一个函数并在其上链接.thens 以实现所需的行为:

const timeout = (cb, ms) => 
  new Promise(resolve => setTimeout(() => resolve(cb()), ms))
;

timeout(() => console.log("a"), 0)
  .then(() => timeout(() => console.log("b"), 1000))
  .then(() => timeout(() => console.log("c"), 1000))
;

console.log("this prints first always because it's synchronous");

就主要的console.log 而言,您的方法行不通,因为您无法将异步代码转换为同步代码。在 JS 开始处理异步任务队列之前,所有同步代码都必须完成运行。您必须通过awaitthen 或使用回调来获取您想要的值,但是您可以通过在回调中返回一些内容并将参数添加到下一个then 回调。

const timeout = (cb, ms) => 
  new Promise((resolve, reject) => 
    setTimeout(() => {
      try {
        resolve(cb());
      }
      catch (err) {
        reject(err);
      }
    }, ms)
  )
;

timeout(() => {
    console.log("a");
    return "b";
  }, 0)
  .then(val =>
    timeout(() => {
      console.log(val);
      return "c";
    }, 1000)
  )
  .then(val =>
    timeout(() => console.log(val), 1000)
  )
  .then(() =>
    timeout(() => {
      throw Error("whoops");
    }, 1000)    
  )
  .catch(err => console.error(err.message))
;

正如 Bergi 在 cmets 中提到的,您需要 try/catch 或将回调链接到返回的 Promise 中,而不是传递它,以便能够正确处理回调抛出的可能性。上面的代码通过在 promise 中调用 reject 来说明这一点。

Bergi 还建议从超时返回值是一种反模式。您可以延迟执行下一个承诺而无需返回,例如:

const wait = ms => 
  new Promise(resolve => setTimeout(resolve, ms))
;

Promise
  .resolve()
  .then(() => {
    console.log("a");
    return wait(1000);
  })
  .then(() => {
    console.log("b");
    return wait(1000);
  })
  .then(() => {
    console.log("c");
  })
  .catch(err => console.error(err))
;

不管怎样,对于这个用例,async/await 应该更干净,特别是如果你想沿着承诺链传递值:

const wait = ms => 
  new Promise(resolve => setTimeout(resolve, ms))
;

(async () => {
  console.log("a");
  await wait(1000);
  console.log("b");
  await wait(1000);
  console.log("c");
})();

【讨论】:

  • 不要传递回调。 (如果它抛出,你的程序崩溃)。请改用timeout(1000).then(() => console.log("b"))
  • @Bergi,那么他应该把什么作为resolve 函数的参数?你能具体解释一下正确的写法是什么吗?
  • 没什么。超时没有有用的结果。充其量,将解析值本身作为参数,但不要通过调用回调函数来生成它。
  • "您需要 try/catch 才能正确处理回调抛出的可能性。" - 然而,您放置了 try 块在您的代码示例中的错误位置。最好不要接受任何回调,这样你就不必处理这个问题。
  • 是的,我知道我搞砸了。对于那个很抱歉。我不想删除,因为你正在交谈,但我会编辑它。我不确定如何清楚地重构以避免回调,同时允许返回值传递给下一个.then
猜你喜欢
  • 2013-05-26
  • 2018-10-13
  • 2019-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-26
  • 1970-01-01
相关资源
最近更新 更多