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

dx_*_*_dt 78 javascript asynchronous promise

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

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

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

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

我想知道是否有任何方法可以从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);
Run Code Online (Sandbox Code Playgroud)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>
Run Code Online (Sandbox Code Playgroud)

jfr*_*d00 62

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

浏览器中当前生成的Javascript没有wait()sleep()允许其他东西运行.所以,你根本无法做你所要求的.相反,它具有异步操作,可以完成它们的操作,然后在完成后调用您(因为您一直在使用promises).

部分原因是因为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);
});
Run Code Online (Sandbox Code Playgroud)

这将以保证顺序返回输出 - 如下所示:

start
middle
end
Run Code Online (Sandbox Code Playgroud)

2018年更新(写完这个答案后三年):

如果您或者您的transpile代码或支持ES7等功能的环境中运行你的代码asyncawait,你现在可以使用await,使您的代码"显示"等待承诺的结果.它仍在以承诺发展.它仍然不会阻止所有Javascript,但它确实允许您以更友好的语法编写顺序操作.

而不是ES6的做事方式:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});
Run Code Online (Sandbox Code Playgroud)

你可以这样做:

// 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
});
Run Code Online (Sandbox Code Playgroud)

  • 这不起作用 someValue 未定义。等待结果是如此复杂,这太疯狂了。 (4认同)
  • 是的,使用`then()`就是我一直在做的事情.我只是不喜欢一直写`function(){...}`.它使代码混乱. (3认同)
  • @dfoverdx - Javascript中的异步编码总是涉及回调,因此您必须始终定义一个函数(匿名或命名).目前没办法解决. (3认同)
  • 虽然您可以使用ES6箭头表示法使其更简洁.`(foo)=> {...}`而不是`function(foo){...}` (2认同)
  • @dfoverdx - 我的回答三年后,我用ES7`async`和`await`语法作为选项更新了它,现在可以在node.js和现代浏览器中或通过转换器获得. (2认同)
  • 锤你的额头(我也有),“async/await”很棒,但本质上只是语法糖([关于这个的很棒的文章](https://medium.com/@bluepnume/learn-about-promises- before-you-start-using-async-await-eb148164a9c8)),它允许您在顶层编写否则会以某种形式的“.then()”– 花体形式编写的内容。事实上,它在被转译时确实如此......仍然无法在命令行 Nodejs 应用程序的真正主线程中等待,以在顶层获得最后一行输出。 (2认同)

Jos*_*das 13

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

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这简单地包含了承诺.async函数将*不*在运行时等待任何事情,它只会返回一个promise.`async` /`await`只是`Promise.then`的另一种语法. (7认同)
  • 感谢您的反馈意见.我删除了Generator示例,并链接到更全面的Generator教程,而不是相关的OSS库. (3认同)

Sta*_*iel 9

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

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

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()
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

  • 这也行不通。这只会让 Promise 有序运行。kickOff() 之后的任何事情都会在 Promise 完成之前运行。 (2认同)