如何等待JavaScript Promise解析后再恢复函数?

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

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

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

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

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

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

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>

答案 1

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

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

部分原因是因为Javascript的单线程性。如果单个线程正在旋转,则在该旋转线程完成之前,没有其他 Javascript 可以执行。ES6引入和生成器将允许一些这样的合作技巧,但是我们离能够在已安装的浏览器的广泛样本中使用它们还有很长的路要走(它们可以用于一些服务器端开发,您可以在其中控制正在使用的JS引擎)。yield


仔细管理基于 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 功能(如 和 )的环境中运行代码,则现在可以使用 使代码“显示”以等待承诺的结果。它仍然在用承诺进行编程。它仍然没有阻止所有的Javascript,但它确实允许你用更友好的语法编写顺序操作。asyncawaitawait

而不是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函数在其函数体内命中第一个 promise 时立即返回一个 promise,因此对于调用方来说,异步函数仍然是非阻塞的,调用方仍然必须处理返回的 promise 并从该 promise 中获取结果。但是,在函数内部,您可以使用 promise 编写更多类似顺序的代码。请记住,只有在等待承诺时才会执行有用的操作,因此为了使用,您的异步操作必须都是基于承诺的。awaitasyncawaitawaitasync/await


答案 2

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

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

如果使用ES2015,则可以使用生成器。如果您不喜欢语法,可以使用异步实用程序函数将其抽象出来,如此处所述

如果使用ES5,你可能会想要像Bluebird这样的库来给你更多的控制。

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