【问题标题】:promise chain does not wait until other promise is resolved承诺链不会等到其他承诺解决
【发布时间】:2021-02-12 00:35:20
【问题描述】:

我想一次执行一个函数,并在一个函数完成时调用另一个函数。我能够使用回调但不使用承诺链来做到这一点。 这是我尝试过的(基于https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#chained_promises),但它同时执行前 3 个函数,而不是在每个函数中等待 1 秒:

function displayAll() {
    var myPromise = (new Promise(display1))
    .then((new Promise(display2))
    .then((new Promise(display3))
    .then(display4)));
}

function display1(resolve) {
    setTimeout(function () {
        console.log("display1");
        resolve();
    }, 1000);
}

function display2(resolve) {
    setTimeout(function () {
        console.log("display2");
        resolve();
    }, 1000);
}

function display3(resolve) {
    setTimeout(function () {
        console.log("display3");
        resolve();
    }, 1000);
}

function display4(resolve) {
    setTimeout(function () {
        console.log("display4");
    }, 1000);
}

你知道代码有什么问题吗?是否可以在没有回调的情况下做我想做的事情?

【问题讨论】:

  • resolve 作为参数传递并不意味着它是一个承诺。您需要将 setTimeouts 包装在一个 Promise 中。
  • 这不是真的,Promise (MDN) 构造函数将function(resolve, reject) 作为参数,所以display1 函数是一个有效参数
  • 这里的主要问题是.then() 要求你向它传递一个函数引用并且你正在向它传递一个promise。这不是您使用.then() 的方式,因此您通过它的承诺会被忽略。 IMO,当他们设计 Promise 时,这应该是一个 TypeError,这显然是一个编程错误,但 .then() 只是忽略了它。相反,您需要传递一个返回您的承诺的函数。这一点非常重要,这样.then() 可以在父 Promise 解决时调用您的函数。
  • 不幸的是 MDN 把你带到了这个地方。如果您不整天盯着这些东西看,该页面上的代码会非常具有误导性。几乎不可能找到以这种方式编写的生产、非人为的应用程序代码。这是其他评论者的信息。此外,虽然答案有效,但它属于同一类别的人为/不会在狂野的例子中找到。
  • 我刚刚向 MDN 提交了这个问题,看看我们是否可以解决这个问题:github.com/mdn/content/issues/2303

标签: javascript promise settimeout


【解决方案1】:

为了链接Promises (MDN),您需要在then 方法回调中返回一个promise,而不是将promise 构造为then 方法的参数。

一旦“遇到”new 关键字,这将触发 Promise,这不是预期的行为。相反,您希望等待第一个 Promise 结束,然后链接 then 方法,该方法将创建一个新的 Promise

function displayAll() {
    var myPromise = (new Promise(display1))
    // .then(new Promise(display2)) <-- you are calling here the promise
    .then(function() {
         return new Promise(display2) // <-- here we return a promise to chain
     })
    .then(()=> new Promise(display3)) // same with arrow functions
    .then(display4);
}

来自您的代码:

function displayAll() {
    var myPromise = (new Promise(display1))
    .then(()=> new Promise(display2))
    .then(() => new Promise(display3))
    .then(display4);
}

function display1(resolve) {
    setTimeout(function () {
        console.log("display1");
        resolve();
    }, 1000);
}

function display2(resolve) {
    setTimeout(function () {
        console.log("display2");
        resolve();
    }, 1000);
}

function display3(resolve) {
    setTimeout(function () {
        console.log("display3");
        resolve();
    }, 1000);
}

function display4(resolve) {
    setTimeout(function () {
        console.log("display4");
    }, 1000);
}

displayAll()

另一种更清晰的方法:

您还可以让您的显示函数返回一个Promise,这样您就可以将它们直接传递给then 方法:

function display1() {
   return new Promise(resolve => {
      setTimeout(function () {
         console.log("display1");
         resolve();
      }, 1000);
   });
}
function display2() {
   return new Promise(resolve => {
      setTimeout(function () {
         console.log("display2");
         resolve();
      }, 1000);
   });
}
function display3() {
   return new Promise(resolve => {
      setTimeout(function () {
         console.log("display3");
         resolve();
      }, 1000);
   });
}
function display4() {
   return new Promise(resolve => {
      setTimeout(function () {
         console.log("display4");
         resolve();
      }, 1000);
   });
}

let myPromise = 
      display1()
        .then(display2)
        .then(display3)
        .then(display4)

【讨论】:

  • 很好的解释。
  • 谢谢,var myPromise = (new Promise(display1)) 中也有不必要的括号,可以写成var myPromise = new Promise(display1)。您的第二种方法确实更清楚,并且允许直接调用像display1() 这样的函数,而不会出现解析未定义的错误。使用箭头函数表达式有什么好处吗?如果我们将它用于承诺,我想最好将它用于 setTimeout 也保持一致?
  • 是的,括号是不必要的,但至少更清楚的是执行顺序。对于箭头函数,在这些简单的示例中几乎没有区别,它只是一种风格选择,但它们并不严格相等。我鼓励您深入了解其他线程中的箭头函数参数:Are 'Arrow Functions' and 'Functions' equivalent / interchangeable?
【解决方案2】:

displayAll 的执行顺序演练:

  1. var myPromise = (new Promise(display1))
    

Promise 构造函数调用display1 设置超时以记录“display1”并解决承诺。这完美地工作,并且尊重最初的 1 秒延迟。

  1.  .then(new Promise(display2))
    
  • displayAll的执行过程中调用myPromisethen方法。
  • then 的参数在调用之前进行评估。
  • 创建then 的promise 参数会导致Promise 构造函数调用display2,这会设置相对于displayAll 的执行时间的超时。
  • 当调用then 时会默默地忽略promise 参数,因为它不是一个函数。 then 在没有可调用参数的情况下使用的默认处理程序传递传入数据或承诺拒绝原因。
  1.  .then(new Promise(display3))
    

    与前面的then 子句的操作相同:设置一个相对于displayAll 的执行时间的计时器,并使用传递数据或拒绝原因的默认处理程序。

  2.  .then(display4)));
    

    display4 注册为处理程序,以便在步骤 3 中返回的 promise 完成时调用。 display4 设置一个可用的计时器来记录“display4”。注意 display4 的参数现在命名错误 - 传递给后续履行处理程序的参数是链中前一个承诺处理程序返回的值。

调用displayAll的预期输出是

  1. 在调用displayAll 后延迟一秒并记录“display1”。
  2. 在调用displayAll 之后再次延迟一秒,并记录“display2”。
  3. 在调用displayAll 之后再次延迟一秒,并记录“display3”。
  4. 当 Promise 链处理作为处理程序执行 display4 时,设置一个计时器来记录“display4” - 因此它会在“display3”之后一秒记录。

错开执行显示函数的一种解决方案是将 Promise 创建从计算 then 参数的位置移到处理程序本身。处理程序可以返回 Promise 以延迟沿着 Promise 链继续执行,直到返回的 Promise 被计时器解决:

 function displayN() {
     return new Promise( resolve => 
         setTimer( function() {
            console.log("displayN");
            resolve();   
         }, 1000)
     );
 }

其他解决方案和方法也是可能的。 Promisifying setTimeout 创建一个在指定时间间隔后解析的 Promise 对于延迟 Promise 链或 async 函数中的步骤很有用。作为一个相当简单的例子:

 function delayPromise( msec) {
     return new Promise(resolve=> setTimeout( resolve, msec));
 }

顺便说一句,promise 链的值是链中最后一个 thencatchfinally 调用返回的 promise - promise 链值可能由函数返回,但实际上在最少很少记录在变量中。

【讨论】:

    猜你喜欢
    • 2018-03-19
    • 1970-01-01
    • 2019-04-29
    • 1970-01-01
    • 1970-01-01
    • 2016-05-06
    • 2017-04-04
    • 1970-01-01
    • 2018-04-02
    相关资源
    最近更新 更多