我正在尝试使用async / await在浏览器上的JavaScript上进行长时间的计算。应该更改代码还是进行任何改进?使用诺言似乎会使它变得复杂。问题的末尾还有两个相关问题。
它正在执行60000!
(阶乘),并且答案太长而无法在屏幕上显示,因此正在显示答案的二进制位数。 (以十进制显示答案的位数对转换而言花费了太长时间)。
迭代版本是:
((请注意:计算要等到3秒后才能显示,以显示UI without的样子)。
(function() {
let startTimeOfProgram = performance.now(),
timer = 3,
element = $("#status-display-content"),
elementTimer = $("#timer");
setInterval(() => { elementTimer.html((Math.round((performance.now() - startTimeOfProgram) / 10) / 100).toFixed(2)) }, 33);
function updateState() {
element.html(timer--).css({ transition: "none" }).css({ opacity: 1 });
if (timer < 0) timer = 3;
// need to be next cycle because cannot change
// transition and have it animated all in the same cycle
setTimeout(function() {
element.css({ transition: "opacity 1s" }).css({ opacity: 0 }).on("transitionend", () => { updateState(); element.off("transitionend"); });
}, 0);
}
updateState();
function binaryLength(n) {
return n.toString(2).length;
}
const occasionalSleeper = (function() {
let lastSleepingTime = performance.now();
return function() {
if (performance.now() - lastSleepingTime > 33) {
lastSleepingTime = performance.now();
return new Promise(resolve => setTimeout(resolve, 0));
} else {
return Promise.resolve();
}
}
}());
async function asyncBigIntFactorial(n) {
let start = performance.now();
n = BigInt(n);
if (n <= 1n) return 1n;
let result = 1n;
for (let i = 2n; i <= n; i++) {
await occasionalSleeper();
result *= i;
}
console.log("Time taken", (performance.now() - start) / 1000);
return result;
}
setTimeout(function() {
let startTimeOfComputation = performance.now();
asyncBigIntFactorial(60000).then(result => {
$("#calculation-result")
.html(`Number of digits of answer in binary: ${binaryLength(result)}<br>Time it took: ${Math.round((performance.now() - startTimeOfComputation) / 10) / 100} seconds`);
});
}, 3000);
}());
#status-display {
background-color: #000;
color: green;
font: 111px Arial, sans-serif;
width: 150px;
height: 150px;
border-radius: 21%;
text-align: center;
line-height: 150px;
}
#timer {
width: 150px;
text-align: center;
font: 15px Arial, sans-serif;
}
#status-display-content {
transition: opacity 1s
}
#calculation-result {
margin-left: .3em;
}
#left-panel, #calculation-result {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="left-panel">
<div id="status-display">
<div id="status-display-content">
</div>
</div>
<div id="timer"></div>
</div>
<div id="calculation-result"></div>
它也位于:http://jsfiddle.net/rxdpbvku/
可以与注释掉await occasionalSleeper()
的版本进行对比,UI在计算期间不响应:http://jsfiddle.net/fhL2gqpn/
基本上,它使用occasionalSleeper()
并在计算过程中调用await occasionalSleeper()
。
const occasionalSleeper = (function() {
let lastSleepingTime = performance.now();
return function() {
if (performance.now() - lastSleepingTime > 33) {
lastSleepingTime = performance.now();
return new Promise(resolve => setTimeout(resolve, 0));
} else {
return Promise.resolve();
}
}
}());
并且计算部分为:
async function asyncBigIntFactorial(n) {
let start = performance.now();
n = BigInt(n);
if (n <= 1n) return 1n;
let result = 1n;
for (let i = 2n; i <= n; i++) {
await occasionalSleeper();
result *= i;
}
console.log("Time taken", (performance.now() - start) / 1000);
return result;
}
递归版本为:
(function() {
let startTimeOfProgram = performance.now(),
timer = 3,
element = $("#status-display-content"),
elementTimer = $("#timer");
setInterval(() => { elementTimer.html((Math.round((performance.now() - startTimeOfProgram) / 10) / 100).toFixed(2)) }, 33);
function updateState() {
element.html(timer--).css({ transition: "none" }).css({ opacity: 1 });
if (timer < 0) timer = 3;
// need to be next cycle because cannot change
// transition and have it animated all in the same cycle
setTimeout(function() {
element.css({ transition: "opacity 1s" }).css({ opacity: 0 }).on("transitionend", () => { updateState(); element.off("transitionend"); });
}, 0);
}
updateState();
function binaryLength(n) {
return n.toString(2).length;
}
const occasionalSleeper = (function() {
let lastSleepingTime = performance.now();
return function() {
if (performance.now() - lastSleepingTime > 33) {
lastSleepingTime = performance.now();
return new Promise(resolve => setTimeout(resolve, 0));
} else {
return Promise.resolve();
}
}
}());
async function asyncBigIntFactorial(n) {
let start = performance.now();
async function factorialHelper(n) {
n = BigInt(n);
if (n <= 1n) return 1n;
await occasionalSleeper();
let simplerAnswer = factorialHelper(n - 1n);
return simplerAnswer.then(async function(a) {
await occasionalSleeper();
return n * a;
});
}
let result = factorialHelper(n);
console.log("Time taken", (performance.now() - start) / 1000);
return result;
}
setTimeout(function() {
let startTimeOfComputation = performance.now();
asyncBigIntFactorial(60000).then(result => {
$("#calculation-result")
.html(`Number of digits of answer in binary: ${binaryLength(result)}<br>Time it took: ${Math.round((performance.now() - startTimeOfComputation) / 10) / 100} seconds`);
});
}, 3000);
}());
#status-display {
background-color: #000;
color: green;
font: 111px Arial, sans-serif;
width: 150px;
height: 150px;
border-radius: 21%;
text-align: center;
line-height: 150px;
}
#timer {
width: 150px;
text-align: center;
font: 15px Arial, sans-serif;
}
#status-display-content {
transition: opacity 1s
}
#calculation-result {
margin-left: .3em;
}
#left-panel, #calculation-result {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="left-panel">
<div id="status-display">
<div id="status-display-content">
</div>
</div>
<div id="timer"></div>
</div>
<div id="calculation-result"></div>
它也位于:http://jsfiddle.net/rxdpbvku/
[递归版本报告几乎立即完成,因为那时仅创建了promise。
一些观察:
如果位于:
return simplerAnswer.then(async function(a) {
await occasionalSleeper();
return n * a;
});
如果await occasionalSleeper()
被注释掉(或者该函数不是异步函数),则对Promise的最终解包似乎将导致其暂停UI。 (我认为这是6万个承诺的最终解包)。 JSFiddle:http://jsfiddle.net/y0827r16/
另一个观察结果是,如果这是一次真正的递归,则将其自身调用6万次将导致堆栈溢出。但是使用异步/等待,它不会。这是为什么?是因为它就像一个解决承诺表或展开承诺的表,所以一个60,000个条目的表根本不是问题。
我还有一个问题是,如果在任何async
函数中,如果该occasionalSleeper()
是由异步标准自动添加的,那么这是否会使程序员不必担心在哪里添加它们?
如评论中所述,对于持久的计算,可以使用Web Workers。这样,您可以保持浏览器的交互性,而不必定期引入setTimeout
“跳跃”。
[为了尽可能轻松地使用Web Worker,我建议使用此通用的“插件”代码,我先前在this answer中建议:
Function.prototype.callAsWorker = function (...args) { return new Promise( (resolve, reject) => { const code = `self.onmessage = e => self.postMessage((${this.toString()}).call(...e.data));`, blob = new Blob([code], { type: "text/javascript" }), worker = new Worker(window.URL.createObjectURL(blob)); worker.onmessage = e => (resolve(e.data), worker.terminate()); worker.onerror = e => (reject(e.message), worker.terminate()); worker.postMessage(args); }); }
现在您可以让网络工作者使用以下语法执行任何pure函数:
myPureAdditionFunction.callAsWorker(null, 1, 2)
.then((sum) => console.log("1+2=" + sum));
如上所述,这消除了注入类似occasionalSleeper()
之类的执行代码的需要。
您的核心功能可以是:
function bigIntFactorial(n) {
n = BigInt(n);
let result = 1n;
for (let i = 2n; i <= n; i++) result *= i;
return result;
}
和电话:
bigIntFactorial.callAsWorker(null, 60000).then(/* handler */);
我修改了您的代码段以使用此方法。我还消除了3秒的磨合时间,就像使用网络工作者一样,您不必担心DOM和绘画事件。
let startTimeOfProgram = performance.now(),
timer = 3,
element = $("#status-display-content"),
elementTimer = $("#timer");
let stopWatch = setInterval(() => {
elementTimer.html((Math.round((performance.now() - startTimeOfProgram) / 10) / 100).toFixed(2))
}, 33);
function updateState() {
element.html(timer--).css({ transition: "none" })
.css({ opacity: 1 });
if (timer < 0) timer = 3;
// need to be next cycle because cannot change
// transition and have it animated all in the same cycle
setTimeout(function() {
element.css({ transition: "opacity 1s" })
.css({ opacity: 0 })
.on("transitionend", () => {
if (stopWatch) updateState();
element.off("transitionend");
});
});
}
updateState();
const binaryLength = n => n.toString(2).length;
Function.prototype.callAsWorker = function (...args) {
return new Promise( (resolve, reject) => {
const code = `self.onmessage = e => self.postMessage((${this}).call(...e.data));`,
blob = new Blob([code], { type: "text/javascript" }),
worker = new Worker(window.URL.createObjectURL(blob));
worker.onmessage = e => (resolve(e.data), worker.terminate());
worker.onerror = e => (reject(e.message), worker.terminate());
worker.postMessage(args);
});
}
function bigIntFactorial(n) {
n = BigInt(n);
let result = 1n;
for (let i = 2n; i <= n; i++) result *= i;
return result;
}
bigIntFactorial.callAsWorker(null, 60000).then(result => {
clearInterval(stopWatch);
stopWatch = null;
$("#calculation-result")
.html(`Number of digits of answer in binary: ${binaryLength(result)}<br>
Time it took: ${Math.round((performance.now() - startTimeOfProgram) / 10) / 100} seconds`);
});
#status-display {
background-color: #000;
color: green;
font: 111px Arial, sans-serif;
width: 150px;
height: 150px;
border-radius: 21%;
text-align: center;
line-height: 150px;
}
#timer {
width: 150px;
text-align: center;
font: 15px Arial, sans-serif;
}
#status-display-content {
transition: opacity 1s
}
#calculation-result {
margin-left: .3em;
}
#left-panel, #calculation-result {
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="left-panel">
<div id="status-display">
<div id="status-display-content">
</div>
</div>
<div id="timer"></div>
</div>
<div id="calculation-result"></div>