【问题标题】:Execute code that deferred function depends on while waiting在等待时执行延迟函数依赖的代码
【发布时间】:2016-09-23 17:26:42
【问题描述】:

在下面的最小示例中,旧内容的替换由setTimeout 推迟,以便用户有时间完成查看。与此同时,正在准备新的内容,以避免在执行潜在的昂贵任务时阻塞用户界面。

var div = document.getElementById('wrapper');
var newContent = document.createElement('ul');

setTimeout(function() {
  var header = div.firstElementChild;
  header.innerHTML = 'New Content';
  header.nextElementSibling.remove();
  div.appendChild(newContent);
}, 2000);

// Make new content while we wait
[1, 10, 100, 1000].forEach(function(x) {
  var li = document.createElement('li');
  li.innerHTML = 'Factorial of ' + x + ' is ' + factorial(x);
  newContent.appendChild(li);
});

function factorial(num) {
  if (num === 0) {
    return 1;
  } else {
    return (num * factorial(num - 1));
  }
}
<div id='wrapper'>
  <h1>Old content</h1>
  <p>Read it before it's gone.</p>
</div>

我对这种方法的担忧是它似乎无法处理 newContent 在更换到期时未准备好。我也不确定这种方法是否会阻塞用户界面,或者setTimeout使用的任务是否会同时执行。

如何确保用户界面在执行潜在的昂贵任务并在完成后立即使用时不被阻塞?

【问题讨论】:

  • 如果您的newContent 是通过一些同步方法准备的,并且您确定timeout 之后的内容的可用性,那么这样做没有害处..
  • 找出时间。生成内容。再找时间。如果不到 2 秒,请等待剩余时间,然后更换。否则,立即替换内容。
  • 对我来说看起来很合理。也许合并淡入/淡出所以内容的变化不是那么微妙
  • 1000 阶乘远远超出了 JS 表示数字的能力。无论如何,“这是否可取”是用户体验问题,而不是编程问题,除非您询问您的特定实现是否是可取的方法。你为什么不需要担心newContent没有准备好?
  • 您的语言不是假设的。它被称为 JavaScript,实现您想要的语言机制称为“承诺”。但是你必须调用它们;它们不会神奇地发生,它们不会在某件事完成之前完成,也不会改变一个不变的事实,即如果你想要完成或准备好某件事,那么它就必须完成或准备好。

标签: javascript settimeout


【解决方案1】:

您的长时间运行的计算会阻塞浏览器,这绝不是一个好主意。因此,你应该把它放在一个 web worker 中。

如今,使用诸如 Promise 之类的异步工具编写异步代码是一种更好的做法。这是一种通用的伪代码级方法:

// Create a promise which fulfills after some # of ms.
function timeout(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Create the worker, kick it off, and 
// return a promise which fulfills when the worker reports a result.
function waitWorker() {
  const worker = new Worker('factorial.js');
  worker.postMessage([1, 10, 100, 1000]);

  return new Promise(resolve => 
    worker.addEventListener('message', event => resolve(event.data))
  );
}

// Wait for both the worker to complete and the two seconds to elapse.
// Then output the data.
Promise.all([timeout(2000), waitWorker()])
  .then(values => output(values[1]);

编写工人作为练习。

使用异步函数

如果您的环境支持,您还可以使用异步函数更简洁地表达这一点,如下所示:

async function calcAndWait() {
  const result = waitWorker(); // Kick off computation.
  await timeout(ms);           // Wait for two seconds.
  output(await result);        // Wait for computation to finish and output.
}

【讨论】:

  • 这是一个优雅的解决方案,我希望广告商能更频繁地使用这种方法,因为他们的计算会在加载期间不断锁定浏览器窗口。
【解决方案2】:

你有两个要求:

  1. 在至少 2 秒过去之前不要隐藏介绍。
  2. 在内容准备好之前不要隐藏介绍。

下面的更改满足了这一点。

<html>
<body>
<div id='wrapper'>
  <h1>Old content</h1>
  <p>Read it before it's gone.</p>
</div>
<script>
var div = document.getElementById('wrapper');
var newContent = document.createElement('ul');
var contentReady = false;
var timesUp = false;

function onContentReady() {
  if (! timesUp || ! contentReady) return;
  var header = div.firstElementChild;
  header.innerHTML = 'New Content';
  header.nextElementSibling.remove();
  div.appendChild(newContent);
}

setTimeout(function() { 
    timesUp = true;
    onContentReady();
  } , 2000);

function makeContent() {
    // Make new content while we wait
    [1, 10, 100, 1000].forEach(function(x) {
      var li = document.createElement('li');
      li.innerHTML = 'Factorial of ' + x + ' is ' + factorial(x);
      newContent.appendChild(li);
    });
    contentReady = true;
    onContentReady();
}

function factorial(num) {
  if (num === 0) {
    return 1;
  } else {
    return (num * factorial(num - 1));
  }
}

setTimeout(function() { 
    makeContent();
  } , 4000);
</script>
</body>
</html>

将此代码中的时间值更改为小于 2 秒,大于 2 秒即可看到。

setTimeout(function() { 
    makeContent();
  } , 4000);

【讨论】:

  • 我接受答案以感谢您的努力,但在我看来,@Mike McCaughan 在 cmets 中建议的解决方案更简洁。
  • 我认为contentReady = true; onContentReady(); 应该在它当前所在的循环之后。
猜你喜欢
  • 2012-02-12
  • 2021-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-16
  • 2016-12-11
相关资源
最近更新 更多