以下是在JavaScript中编写需要在浏览器上运行的长计算的正确方法吗?

问题描述 投票:1回答:1

我正在尝试使用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()是由异步标准自动添加的,那么这是否会使程序员不必担心在哪里添加它们?

javascript async-await es6-promise ecmascript-2017
1个回答
0
投票

如评论中所述,对于持久的计算,可以使用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>
© www.soinside.com 2019 - 2024. All rights reserved.