如何在恢复功能之前等待 JavaScript Promise 解决?

IT技术 javascript asynchronous promise async-await ecma
2021-01-12 22:36:07

我正在做一些单元测试。测试框架将页面加载到 iFrame 中,然后针对该页面运行断言。在每个测试开始之前,我创建一个Promise将 iFrame 的onload事件设置为 call resolve(),设置 iFrame 的src,并返回Promise。

所以,我可以只调用loadUrl(url).then(myFunc),它会在执行任何myFunc内容之前等待页面加载

我在测试中到处使用这种模式(不仅仅是用于加载 URL),主要是为了允许对 DOM 进行更改(例如,模仿单击按钮,并等待 div 隐藏和显示)。

这种设计的缺点是我经常用几行代码编写匿名函数。此外,虽然我有一个变通方法(QUnit 的assert.async()),但定义Promise 的测试函数会在运行Promise之前完成。

我想知道是否有任何方法可以从 a 获取值Promise或等待(阻塞/睡眠)直到它解决,类似于 .NET 的IAsyncResult.WaitHandle.WaitOne(). 我知道 JavaScript 是单线程的,但我希望这并不意味着函数不能屈服。

从本质上讲,有没有办法让以下内容以正确的顺序吐出结果?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>

3个回答

我想知道是否有任何方法可以从 Promise 获取值或等待(阻塞/睡眠)直到它解决,类似于 .NET 的 IAsyncResult.WaitHandle.WaitOne()。我知道 JavaScript 是单线程的,但我希望这并不意味着函数不能屈服。

在浏览器目前这一代的Javascript不具有wait()sleep()允许其他事情跑。所以,你根本无法做到你所要求的。相反,它具有异步操作,可以完成它们的工作,然后在它们完成时调用您(正如您一直在使用 Promise 那样)。

部分原因是 Javascript 的单线程。如果单个线程正在旋转,则在该旋转线程完成之前,其他 Javascript 都无法执行。ES6 引入了yield生成器,它们将允许一些类似的协作技巧,但我们距离能够在大量已安装的浏览器中使用它们还有很长的路要走(它们可以在一些服务器端开发中使用,您可以在其中控制 JS 引擎正在使用)。


仔细管理基于 Promise 的代码可以控制许多异步操作的执行顺序。

我不确定我是否完全理解您在代码中尝试实现的顺序,但是您可以使用现有kickOff()函数执行类似的操作,然后.then()在调用它后为其附加一个处理程序:

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

这将以有保证的顺序返回输出 - 像这样:

start
middle
end

2018 年更新(撰写此答案三年后):

如果您转换代码或在支持 ES7 特性(例如async和 )的环境中运行代码await,您现在可以使用await使您的代码“出现”以等待Promise的结果。它仍然是用 Promise 进行编程。它仍然不会阻止所有 Javascript,但它确实允许您以更友好的语法编写顺序操作。

而不是 ES6 的做事方式:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

你可以这样做:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

async一旦第一个await在函数体内命中,函数就会返回一个Promise,因此对于调用者来说,异步函数仍然是非阻塞的,并且调用者仍然必须处理返回的Promise并从该Promise中获取结果。但是,在async函数内部,您可以使用awaiton Promise编写更多类似顺序的代码请记住,await只有当您等待Promise时才会做一些有用的事情,因此为了使用async/await,您的异步操作必须全部基于Promise。

这不起作用 someValue 未定义。试图等待结果是如此复杂,这太疯狂了。
2021-03-12 22:36:07
@dfoverdx - 在我回答三年后,我用 ES7asyncawait语法作为一个选项更新了它,现在可以在 node.js 和现代浏览器中或通过转译器使用。
2021-03-20 22:36:07
@dfoverdx - Javascript 中的异步编码始终涉及回调,因此您始终必须定义一个函数(匿名或命名)。目前没有办法解决。
2021-03-22 22:36:07
虽然你可以使用 ES6 箭头符号来使它更简洁。(foo) => { ... }代替function(foo) { ... }
2021-04-03 22:36:07
对,使用then()是我一直在做的。我只是不喜欢不得不一直写作function() { ... }它使代码混乱。
2021-04-05 22:36:07

如果使用ES2016可以用asyncawait做类似:

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

如果使用 ES2015,您可以使用Generators如果你不喜欢的语法,你可以将它抽象掉使用async效用函数这里解释

如果使用 ES5,您可能需要像Bluebird这样的库来为您提供更多控制。

最后,如果您的运行时支持 ES2015,已经可以使用Fetch Injection以并行方式保留执行顺序

感谢您的反馈意见。我删除了 Generator 示例并链接到更全面的 Generator 教程,其中包含相关的 OSS 库。
2021-03-18 22:36:07
这只是包装了Promise。异步函数在你运行时不会等待任何东西,它只会返回一个Promise。async/await只是Promise.then.
2021-04-02 22:36:07
你是绝对正确的async不会等待......除非它得到使用awaitES2016/ES7 示例仍然无法运行,所以这里是我三年前编写的一些代码来演示该行为。
2021-04-04 22:36:07
您的生成器示例不起作用,它缺少跑步者。当您可以转译 ES8 async/时,请不要再推荐生成器await
2021-04-05 22:36:07

另一种选择是使用 Promise.all 等待一系列 promise 被解析,然后对它们采取行动。

下面的代码显示了如何等待所有 promise 得到解决,然后在它们都准备好后处理结果(因为这似乎是问题的目标);同样出于说明目的,它显示了执行期间的输出(中间结束前结束)。

function append_output(suffix, value) {
  $("#output_"+suffix).append(value)
}

function kickOff() {
  let start = new Promise((resolve, reject) => {
    append_output("now", "start")
    resolve("start")
  })
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => {
      append_output("now", " middle")
      resolve(" middle")
    }, 1000)
  })
  let end = new Promise((resolve, reject) => {
    append_output("now", " end")
    resolve(" end")
  })

  Promise.all([start, middle, end]).then(results => {
    results.forEach(
      result => append_output("later", result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Updated during execution: <div id="output_now"></div>
Updated after all have completed: <div id="output_later"></div>

这也行不通。这只会使Promise按顺序运行。kickOff() 之后的任何内容都将在 promise 完成之前运行。
2021-03-16 22:36:07
顺便说一句:你可以分叉这个小提琴并使用代码:jsfiddle.net/akasek/4uxnkrez/12
2021-03-19 22:36:07
@PhilipRego - 正确,您之后想要运行的任何内容都需要在 Promises.all 然后块中。我已经更新了代码以在执行期间附加到输出(在中间之前结束打印 - Promise可能会乱序),但是 then 块等待它们全部完成,然后按顺序处理结果。
2021-03-26 22:36:07