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


108

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

因此,我可以调用loadUrl(url).then(myFunc),它将在执行任何myFunc操作之前等待页面加载。

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

这种设计的缺点是,我不断编写带有几行代码的匿名函数。此外,尽管有变通方法(QUnit assert.async()),但定义诺言的测试函数在诺言运行之前就已完成。

我想知道是否有任何方法可以从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>


如果将append调用放入可重用的函数中,则可以根据需要then()进行DRY。您还可以制作多用途处理程序,以此来处理then()调用,例如.then(fnAppend.bind(myDiv)),可以大大减少匿名操作。
dandavis'3

您在测试什么?如果它是现代浏览器,或者您可以使用BabelJS之类的工具来转换您的代码,那肯定是可能的。
2015年

Answers:


81

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

浏览器中的当前Javascript版本没有wait()sleep()允许运行其他内容。因此,您根本无法满足您的要求。相反,它具有异步操作,它将执行其操作,然后在完成操作后调用您(就像您一直在使用promise一样)。

部分原因是由于Javascript的单线程性。如果单线程正在旋转,则直到该旋转线程完成之后,其他Javascript才能执行。ES6引入了yield一些生成器和生成器,这些生成器将允许一些类似的协作技巧,但是我们可以通过多种方式在已安装的浏览器中使用它们(它们可以用于控制JS引擎的某些服务器端开发中)正在使用)。


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

我不确定我确切了解您要在代码中实现的顺序,但是您可以使用现有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可以使代码“出现”以等待诺言的结果。它仍在发展中。它仍然不会阻止所有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
});

3
是的,使用then()是我一直在做的。我只是不喜欢一直写function() { ... }。它使代码混乱。
dx_over_dt

3
@dfoverdx-Javascript中的异步编码始终涉及回调,因此您始终必须定义一个函数(匿名或命名)。目前无法解决。
jfriend00

2
虽然您可以使用ES6箭头符号来使其更简洁。(foo) => { ... }代替function(foo) { ... }
Rob H

1
您经常可以在@RobH注释中添加注释,foo => single.expression(here)以消除大括号和大括号。
begie

3
@dfoverdx-回答三年后,我用ES7 asyncawait语法作为选项进行了更新,现在可以在node.js和现代浏览器中或通过编译器使用。
jfriend00 '18

16

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

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

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

如果使用ES5,您可能希望像Bluebird这样的库为您提供更多控制权。

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


1
您的生成器示例不起作用,因为缺少运行程序。当您只需转换ES8 async/ 时,请不要再推荐生成器await
Bergi

3
感谢您的反馈意见。我已经删除了Generator示例,并链接到了一个更全面的Generator教程,其中包含相关的OSS库。
Josh Habdas

10
这只是兑现了承诺。异步函数在运行时不会等待任何东西,它只会返回一个Promise。async/ await只是的另一种语法Promise.then
rustyx

绝对正确,您async将等不及...除非它产生使用await。ES2016 / ES7示例仍然无法如此运行,因此这是我三年前编写的一些代码,用于演示行为。
乔什·哈布达斯

7

另一个选择是使用Promise.all等待一系列的承诺解决。下面的代码显示了如何等待所有的诺言解决,然后在结果全部准备好后处理结果(这似乎是问题的目的);但是,start显然可以在Middle尚未解决之前输出,只需在调用resolve之前添加该代码即可。

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

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


1

您可以手动执行。(我知道,那不是很好的解决方案,但是..)使用while循环直到result没有值为止

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});

这将在等待时消耗整个cpu。
Lukasz Guminski

这是一个可怕的解决方案
JSON,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.