ChatGPT解决这个技术问题 Extra ChatGPT

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

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

因此,我可以只调用 loadUrl(url).then(myFunc),它会等待页面加载,然后再执行 myFunc 的任何内容。

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

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

我想知道是否有任何方法可以从 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 结果 = getResultFrom(promise); $("#output").append(result);

如果将附加调用放入可重用函数中,则可以根据需要 then() 进行 DRY。您还可以制作由 this 引导的多功能处理程序,以提供 then() 调用,例如 .then(fnAppend.bind(myDiv)),这可以大大减少匿名。
你用什么测试?如果它是一个现代浏览器,或者你可以使用像 BabelJS 这样的工具来转换你的代码,这当然是可能的。

j
jfriend00

我想知道是否有任何方法可以从 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 功能(例如 asyncawait)的环境中运行代码,您现在可以使用 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 在其函数体内被击中后立即返回一个承诺,因此对于调用者来说,异步函数仍然是非阻塞的,调用者仍然必须处理返回的承诺并从该承诺中获取结果.但是,在 async 函数中,您可以在 Promise 上使用 await 编写更多类似顺序的代码。请记住,await 仅在您等待承诺时才会做一些有用的事情,因此为了使用 async/await,您的异步操作必须全部基于承诺。


对,使用 then() 是我一直在做的。我只是不喜欢一直写function() { ... }。它使代码混乱。
@dfoverdx - Javascript 中的异步编码总是涉及回调,所以你总是必须定义一个函数(匿名或命名)。目前没有办法解决。
尽管您可以使用 ES6 箭头符号来使其更简洁。 (foo) => { ... } 而不是 function(foo) { ... }
@dfoverdx - 在我回答三年后,我使用 ES7 asyncawait 语法作为选项更新了它,现在可以在 node.js 和现代浏览器中或通过转译器使用。
这不起作用 someValue 未定义。试图等待结果真是太复杂了。
v
vhs

如果使用 ES2016,您可以使用 asyncawait 并执行以下操作:

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

如果使用 ES2015,您可以使用 Generators。如果您不喜欢该语法,您可以使用 async utility function 作为 explained here 将其抽象出来。

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

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


您的生成器示例不起作用,它缺少跑步者。当您只能转译 ES8 async/await 时,请不要再推荐生成器。
感谢您的反馈意见。我删除了 Generator 示例,并改为链接到更全面的 Generator 教程以及相关的 OSS 库。
这只是简单地包装了承诺。当你运行 async 函数时,它不会等待任何东西,它只会返回一个 Promise。 async/await 只是 Promise.then 的另一种语法。
S
Stan Kurdziel

另一种选择是使用 Promise.all 等待一系列承诺解决,然后对其采取行动。

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

函数 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() 执行期间更新:

全部完成后更新:


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