【问题标题】:execute .innerHTML in a function在函数中执行 .innerHTML
【发布时间】:2019-04-11 15:20:26
【问题描述】:

我想在 js 代码结束之前更改我的 HTML。

我尝试使用异步函数,但我不确定这是否是正确的方法。

function wait(ms){
  var start = new Date().getTime();
  var end = start;
  while(end < start + ms) {
    end = new Date().getTime();
  }
}

function one() {
  wait(1000);
  document.getElementById("1a").innerHTML = "bla";
}

function two() {
  wait(1000);
  document.getElementById("2b").innerHTML = "blabla";
}

function three() {
  wait(1000);
  document.getElementById("3c").innerHTML = "blablabla";
}

function start() {
  one();
  two();
  three();
} start();

代码等待 3 秒,然后更新我的 div。

我希望我的代码:

等待 1 秒, 更新 div, 等待 1 秒, 更新 div, 等待 1 秒, 更新div

【问题讨论】:

  • 您没有提供明确的问题陈述。
  • Javascript 使用单线程,这意味着您的“等待”实际上并不像您认为的那样工作
  • 给定的代码有什么问题?它是否按预期工作?
  • 使用间隔....等待方法只是冻结浏览器。
  • @DerekPollard JavaScript 本身并不是异步的。这就是为什么 wait 实际上会阻塞。

标签: javascript asynchronous innerhtml


【解决方案1】:

您可以尝试使用setTimeout

使用函数

我制作了这个函数,它可以轻松提供要更新的对象数组。

/* 
  timed_elemet_updates(array, int)
  `elements` should be an array of objects which contains the
  ID and HTML you want to update.
*/
function timed_element_updates(elements, seconds) {
      var element = elements.shift();
      document.getElementById(element.id).innerHTML = element.html;
      setTimeout(timed_element_updates, (seconds * 1000), elements, seconds);
}

示例用法:

function start() {
    var elements = [
        {id: '1a', html: 'bla'},
        {id: '2b', html: 'blabla'},
        {id: '3c', html: 'blablabla'},
    ];
    timed_element_updates(elements, 1);
}
start();

这个答案更简洁(重复的代码行更少),更易于使用(只需向数组中添加更多元素),更可重用(每个元素都没有新功能)

原答案

function one() {
    document.getElementById("1a").innerHTML = "bla";
    setTimeout(two, 1000);
}

function two() {
    document.getElementById("2b").innerHTML = "blabla";
    setTimeout(three, 1000);
}

function three() {
  document.getElementById("3c").innerHTML = "blablabla";
}

one();

这将调用函数one(),然后在1000毫秒(1秒)后调用two(),然后在1000毫秒(1秒)后调用three()

【讨论】:

  • @Vx0 没问题,很高兴能帮上忙
  • @Vx0 我为我的答案添加了(我认为是)一个更好的解决方案。
  • @GrumpyCrouton:也更新了我的答案以添加更多背景信息
【解决方案2】:

这可以通过Promises 解决:

function showTextAfterMS (text, elemSelector, ms) {
  return new Promise((res, rej) => {
    let elem = document.querySelector(elemSelector);
    if (!elem) {
      return rej(new Error(`Cannot find element by selector: ${elemSelector}`));
    }
    
    setTimeout(() => {
      elem.innerHTML = text;
      res(elem);
    }, ms);
  });
}



showTextAfterMS('bla', '#el1', 1000).
  then(() => showTextAfterMS('blabla', '#el2', 1000)).
  then(() => showTextAfterMS('blablabla', '#el3', 1000));
<div id="el1"></div>
<div id="el2"></div>
<div id="el3"></div>

您也可以使用setTimeoutsetInterval,但我的经验是使用Promise 更可靠/更稳定。

要立即显示第一个文本,只需将第一个调用 showTextAfterMS 更改为 showTextAfterMS('bla', '#el1', 0)

编辑

之所以使用Promises 是正确的解决方案,最终根源于 JavaScript 的运行时概念。简而言之,这是因为从技术上讲,setTimeoutsetInterval 都是 异步操作,因为它们都由 JavaScript 事件循环处理。关于事件循环和 JavaScript 的通用并发模型的详尽解释可以在 MDN 上找到。

简而言之:必须执行的每个操作都被推到运行时必须执行的操作的Queue 的末尾,......好吧,运行。这样,来自 UI 的操作以及其他操作(如超时和间隔)都会得到处理。运行时会逐步处理这些操作,但是,它们可能需要不同的时间来完成。这是因为运行时一直运行到完成,这意味着每个操作都在完成处理后处理。因为setTimeoutsetInterval 产生的动作被放置在Queue 上,毫秒数并不是调用相应函数的保证时间。这是在执行之前经过的保证最短时间。这使得它们都产生异步操作

但是,从架构的角度来看,您需要一种可靠且可扩展的方式来对异步操作进行排序。这就是Promises 发挥作用的地方。

稍微挥手,我们可以说我们可以通过使用回调函数得到相同的解决方案没有 Promise,因为事件循环的工作方式。它已经一次调用一个函数,对吧?所以这里有一个基于回调的“平等”解决方案:

// A "Promise equivalent", setTimeout based function with callbacks
function showTextAfter (ms, text, elemSelector, onComplete, onError) {
  if (typeof ms !== 'number' || isNaN(ms)) {
    return onError(new Error(`MS needs to be number, got: ${ms}`));
  }
  if (typeof text !== 'string') {
    return onError(new Error(`Expected TEXT to be String, got: ${text}`));
  }
  let elem = document.querySelector(elemSelector);
  if (!elem) {
    return onError(new Error(`Cannot find element: ${elemSelector}`));
  }

  setTimeout(() => {
    elem.innerHTML = text;
    onComplete(elem);
  }, ms);
}



showTextAfter(1000, 'bla', '#el1', (elem1) => {
  showTextAfter(1000, 'blabla', '#el2', (elem2) => {
    showTextAfter(1000, 'blablabla', '#el3', (elem3) => {
      // do whatever you want with the elements. this example
      // discards them
    });
  });
});
<div id="el1"></div>
<div id="el2"></div>
<div id="el3"></div>

它同样运作良好,并允许您以可靠的方式链接您的操作。它的缺点是:

  • 它不能很好地扩展。要链接更多动作,您需要更深的嵌套,因为必须在成功回调函数中调用新动作
  • 您必须手动进行嵌套。因此,你必须嵌套得越深,就越难跟踪你的代码流。这就是为什么人们称它为“末日金字塔”。想象一下,必须为 20 个元素混合文本。
  • 看看它的签名:showTextAfter :: Number -&gt; String -&gt; String -&gt; Function -&gt; Function -&gt; undefined。对于这个微不足道的小功能来说,这是很多东西!只传递前 3 个参数不是很酷吗?

我们可以通过从对 showTextAfter 的调用返回一个新函数来缓解最后一个问题,该函数使用 onCompleteonError 回调:

function showTextAfter (ms, text, elemSelector) {
  return function (onComplete, onError) { // <-- this little fellow here is what it's all about
    if (typeof ms !== 'number' || isNaN(ms)) {
      return onError(new Error(`MS needs to be number, got: ${ms}`));
    }
    if (typeof text !== 'string') {
      return onError(new Error(`Expected TEXT to be String, got: ${text}`));
    }
    let elem = document.querySelector(elemSelector);
    if (!elem) {
      return onError(new Error(`Cannot find element: ${elemSelector}`));
    }

    setTimeout(() => {
      elem.innerHTML = text;
      onComplete(elem);
    }, ms);
  }
}


const showEl1 = showTextAfter(1000, 'bla', '#el1');
const showEl2 = showTextAfter(1000, 'blabla', '#el2');
const showEl3 = showTextAfter(1000, 'blablabla', '#el3');

showEl1(elem1 => {
  showEl2(elem2 => {
    showEl3(elem3 => {
      // whatever
    });
  });
});
<div id="el1"></div>
<div id="el2"></div>
<div id="el3"></div>

是的,这样更好。但是,它并没有真正解决问题,对吧?

不过不要惊慌,因为这些正是Promises 解决的问题!它们允许您以可扩展的方式对异步操作进行排序,从而更容易跟踪控制流。上述所有问题都可以通过拥有一个(本机)“就地”返回值来消除,您可以“链接”其他异步步骤,这些步骤将“在未来”完成(意味着它们可以成功完成,或者可以用 @ 完成) 987654348@)。真正聪明的是,Promise 允许您链接下一个异步操作无需嵌套

查看我最初的(稍作改动)答案:

function showTextAfterMS (ms, text, elemSelector) {
  return new Promise((onComplete, onError) => {

    // type checking stuff...

    let elem = document.querySelector(elemSelector);
    setTimeout(() => {
      elem.innerHTML = text;
      onComplete(elem);
    }, ms);
  });
}



showTextAfterMS(1000, 'bla', '#el1'). // <-- no more nesting!
  then(() => showTextAfterMS(1000, 'blabla', '#el2')).
  then(() => showTextAfterMS(1000, 'blablabla', '#el3'));
<div id="el1"></div>
<div id="el2"></div>
<div id="el3"></div>

【讨论】:

  • 这么简单的事情对我来说似乎有点矫枉过正
  • 我真的很想知道为什么这被否决了。 @GrumpyCrouton“过度杀伤”很大程度上取决于您的意见,不是吗?这是一段可重用的代码,可以完美地完成工作,可以链接N 元素,而不必为每个元素编写超过一行。通过查看您的答案,您必须复制并粘贴每个元素 3 行,这使得它不是很可重用,是吗?从这个角度来看,你的解决方案也太过分了
  • 这真的取决于它是否需要 可重用,但是您的函数本身就更复杂,并且使用的代码行数与我的相同。 Promise 的概念也比 setTimeout 更难掌握。我并不是说您的解决方案不好(这是一个很好的解决方案),而且我绝对没有反对它(事实上,我赞成它)。虽然我认为如果您解释 Promise 的工作原理,您的答案会好多。别担心,我的也被否决了,没有任何理由发布。
  • @GrumpyCrouton 并不是说​​你投了反对票。这就是为什么我把你的名字放在我最初的陈述之后。对困惑感到抱歉!关于代码量:我的意思是,如果 OP 想要显示 - 比如说 - 随着时间的推移 10 个文本片段,您的解决方案的代码大小会增长得更快。是的,你说得对,Promises 比 setTimeout/setInterval 更难掌握,但它们异步处理的正确工具。无论如何,两种解决方案都能完成工作,只是方式不同。 PS:赞成你的,因为它做了OP想要的:)
  • 是的,我认为如果 OP 需要显示更多的文本片段,则需要一个更可重用的解决方案
【解决方案3】:

您可以利用async functions

function one() {
  console.log('document.getElementById("1a").innerHTML = "bla";')
}

function two() {
  console.log('document.getElementById("2b").innerHTML = "blabla";')
}

function three() {
  console.log('document.getElementById("3c").innerHTML = "blablabla";')
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function demo() {
  await sleep(1000);
  one();
  await sleep(1000);
  two();
  await sleep(1000);
  three();
}

demo();

参考:https://javascript.info/async-await

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-20
    • 2019-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-11
    相关资源
    最近更新 更多