在恢复函数之前,如何等待JavaScript承诺得到解决?

在恢复函数之前,如何等待JavaScript承诺得到解决?,javascript,asynchronous,promise,async-await,ecma,Javascript,Asynchronous,Promise,Async Await,Ecma,我正在做一些单元测试。测试框架将页面加载到iFrame中,然后对该页面运行断言。在每个测试开始之前,我创建一个Promise,它将iFrame的onload事件设置为调用resolve(),设置iFrame的src,并返回Promise 因此,我可以调用loadUrl(url)。然后(myFunc),它将在执行myFunc之前等待页面加载 我在测试中到处使用这种模式(不仅仅是加载URL),主要是为了允许对DOM进行更改(例如,模仿单击按钮,等待div隐藏和显示) 这种设计的缺点是,我经常编写匿名

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

因此,我可以调用
loadUrl(url)。然后(myFunc)
,它将在执行
myFunc
之前等待页面加载

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

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

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

本质上,有没有一种方法可以让下面的内容以正确的顺序输出结果

功能启动(){
返回新承诺(功能(解决、拒绝){
$(“#输出”)。追加(“开始”);
setTimeout(函数(){
解决();
}, 1000);
}).然后(函数(){
$(“#输出”)。追加(“中间”);
返回“结束”;
});
};
函数getResultFrom(承诺){
//待办事项
返回“结束”;
}
var promise=kickOff();
var结果=getResultFrom(承诺);
$(“#输出”)。追加(结果)

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

当前一代浏览器中的Javascript没有允许其他东西运行的
wait()
sleep()
。所以,你根本不能按你的要求去做。相反,它具有异步操作,这些操作将完成它们的工作,然后在完成后调用您(正如您使用Promissions所做的那样)

部分原因是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
wait
)的环境中运行代码,现在可以使用
wait
使代码“显示”以等待承诺的结果。它仍然在用承诺编程。它仍然不会阻止所有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
函数在其函数体中命中第一个
wait
后立即返回一个承诺,因此对于调用者来说,异步函数仍然是非阻塞的,调用者仍然必须处理返回的承诺并从该承诺中获得结果。但是,在
async
函数中,您可以使用
await
on promissions编写更多类似顺序的代码。请记住,
await
仅在等待承诺时才做有用的事情,因此为了使用
async/await
,您的异步操作都必须基于承诺。

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

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())
如果使用ES2015,您可以使用。如果您不喜欢语法,可以使用as将其抽象掉

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


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

另一个选项是使用Promise.all来等待一系列承诺得到解决,然后根据这些承诺采取行动

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

函数附加输出(后缀、值){
$(“#输出"+后缀)。追加(值)
}
函数启动(){
开始=新承诺((解决、拒绝)=>{
追加输出(“现在”、“开始”)
解决(“开始”)
})
让中间=新承诺((解决、拒绝)=>{
设置超时(()=>{
追加输出(“现在”、“中间”)
解决(“中间”)
}, 1000)
})
让结束=新承诺((解决、拒绝)=>{
追加输出(“现在”、“结束”)
决定(“结束”)
})
承诺。所有([开始、中间、结束])然后(结果=>{
结果:forEach(
结果=>append_输出(“稍后”,结果))
})
}
启动()

在执行期间更新:
更新
kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});