160

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

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

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

这种设计的缺点是我经常用几行代码编写匿名函数。此外,虽然我有一个解决方法(QUnit's 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>

4

3 回答 3

109

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

浏览器中当前一代的 Javascript 没有允许其他东西运行的wait()or 。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在其函数体内第一个被击中就会返回一个承诺,因此对于调用者来说,异步函数仍然是非阻塞的,调用者仍然必须处理返回的承诺并从该承诺中获取结果。但是,在函数内部,您可以使用on Promiseasync编写更多类似顺序的代码。await请记住,await只有在您等待承诺时才会做一些有用的事情,因此为了使用async/await,您的异步操作必须都是基于承诺的。

于 2015-03-08T00:23:21.800 回答
20

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

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

如果使用 ES2015,您可以使用Generators。如果您不喜欢该语法,您可以使用此处解释的async实用函数将其抽象出来。

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

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

于 2017-03-04T11:11:44.993 回答
10

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

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

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>

于 2018-05-11T22:40:26.717 回答