我正在做一些单元测试。测试框架将页面加载到 iFrame 中,然后针对该页面运行断言。在每个测试开始之前,我创建一个 Promise
,它将 iFrame 的 onload
事件设置为调用 resolve()
,设置 iFrame 的 src
,并返回承诺。
因此,我可以只调用 loadUrl(url).then(myFunc)
,它会等待页面加载,然后再执行 myFunc
的任何内容。
我在我的测试中到处使用这种模式(不仅仅是为了加载 URL),主要是为了允许对 DOM 进行更改(例如,模仿单击按钮,并等待 div 隐藏和显示)。
这种设计的缺点是我经常用几行代码编写匿名函数。此外,虽然我有一个解决方法(QUnit 的 assert.async()
),但定义承诺的测试函数在承诺运行之前完成。
我想知道是否有任何方法可以从 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 结果 = getResultFrom(promise); $("#output").append(result);
.then(fnAppend.bind(myDiv))
,这可以大大减少匿名。
我想知道是否有任何方法可以从 Promise 中获取值或等待(阻塞/睡眠)直到它解决,类似于 .NET 的 IAsyncResult.WaitHandle.WaitOne()。我知道 JavaScript 是单线程的,但我希望这并不意味着函数不能产生。
当前一代浏览器中的 Javascript 没有允许其他东西运行的 wait()
或 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
在其函数体内被击中后立即返回一个承诺,因此对于调用者来说,异步函数仍然是非阻塞的,调用者仍然必须处理返回的承诺并从该承诺中获取结果.但是,在 async
函数中,您可以在 Promise 上使用 await
编写更多类似顺序的代码。请记住,await
仅在您等待承诺时才会做一些有用的事情,因此为了使用 async/await
,您的异步操作必须全部基于承诺。
如果使用 ES2016,您可以使用 async
和 await
并执行以下操作:
(async () => {
const data = await fetch(url)
myFunc(data)
}())
如果使用 ES2015,您可以使用 Generators。如果您不喜欢该语法,您可以使用 async
utility function 作为 explained here 将其抽象出来。
如果使用 ES5,您可能需要像 Bluebird 这样的库来提供更多控制权。
最后,如果您的运行时已支持 ES2015,则可以使用 Fetch Injection 以并行方式保留执行顺序。
async
/await
时,请不要再推荐生成器。
async
/await
只是 Promise.then
的另一种语法。
另一种选择是使用 Promise.all 等待一系列承诺解决,然后对其采取行动。
下面的代码显示了如何等待所有承诺解决,然后在它们都准备好后处理结果(因为这似乎是问题的目标);同样出于说明目的,它显示了执行期间的输出(在中间之前结束)。
函数 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() 执行期间更新:
全部完成后更新:
then()
是我一直在做的。我只是不喜欢一直写function() { ... }
。它使代码混乱。(foo) => { ... }
而不是function(foo) { ... }
async
和await
语法作为选项更新了它,现在可以在 node.js 和现代浏览器中或通过转译器使用。