如何同步运行嵌套循环?

时间:2018-06-29 16:27:53

标签: javascript for-loop asynchronous nested-loops

我正在编写一个简单的脚本,该脚本一次将一个字符输出到屏幕上。

我正在这样做,以便该函数(我称为slowPrint)可以接收字符串数组。数组中的每个元素代表一条消息。

这是我到目前为止的代码:

但是,我没有得到预期的输出。 我怀疑这部分是由于代码的异步性质,尽管我对正在发生的事情以及如何解决它没有完全清楚的了解。

首先,<br />标签是在任何消息之前打印的,这告诉我外循环在嵌套消息甚至开始之前就已经完成了。

但是,当嵌套循环开始时,数组中的每个字符串都间隔一秒钟打印一次,但要完整打印而不是逐字符打印

我想念什么?

另外,有人可以解释setTimeout方法的以下行为吗?

方案1 :当我将第二个参数设置为i * 1000时,第二个字符串打印一个接一个的第二个(同样,整个字符串而不是一个字符一个字符)

     const messages = [
      "all systems are operational",
      "you may proceed"
    ];

    function slowPrint(args) {

      let screen = document.getElementById('screen');

      for (let i = 0; i < args.length; i++) {

        let message = args[i];

        for (let j = 0; j < message.length; j++) {
          setTimeout(function () {
            screen.innerHTML += message[j];
          }, i * 1000);
        }

        screen.innerHTML += '<br />';

      }

    }

    slowPrint(messages)
<div id="screen"></div>

方案2 :当我将第二个参数设置为j * 1000时,输出完全出乎意料:每隔两个字符以2组为一组打印,但是顺序难以理解;只有最后一个参数的最后一个单词会像其他所有内容一样打印出来。

方案3 :当我将第二个参数设置为1000时,数组中的所有字符串将在一秒钟后打印出来。

发生了什么事?

4 个答案:

答案 0 :(得分:1)

使用async function和名为sleep()的帮助程序函数将setTimeout()包裹在Promiseawait中,您可以进行最小的更改

const messages = [
  'all systems are operational',
  'you may proceed'
];

const sleep = ms => new Promise(resolve => { setTimeout(resolve, ms) })

async function slowPrint(args) {
  let screen = document.getElementById('screen');

  for (let i = 0; i < args.length; i++) {
    let message = args[i];

    for (let j = 0; j < message.length; j++) {
      await sleep(100);
      screen.innerHTML += message[j];
    }

    screen.innerHTML += '<br />';
  }
}

slowPrint(messages)
<div id="screen"></div>

setTimeout()的回调是异步执行的,因此执行顺序将始终如下所示:

// first

setTimeout(function () {
  // at *least* after all the current synchronous code has completely finished
})

// second

如评论中所述,async / await仅在实现browsersECMAScript 2017中受支持。

答案 1 :(得分:1)

此视频是js在浏览器中的工作方式的最佳解释之一:here

基本上,您放置在setTimeout回调中的任何内容都会以传递给第二个参数的ms数的形式放置在backburner上。然后将其放入回调队列中,直到调用堆栈为空,并且它是队列中的下一个项目

如果将代码复制并粘贴到http://latentflip.com/loupe/中,您将看到它在幕后的实际运行方式

答案 2 :(得分:1)

只需使用setInterval,即可使用简洁的代码来完成此操作。您只需要适当地管理索引。此代码使用i遍历每个字母,并使用j遍历数组。当i达到限制时,j会增加;当j达到限制时,间隔将被清除。

let screen = document.getElementById('screen');
const messages = [
    "all systems are operational",
    "you may proceed"
  ];

function slowPrint(args) {
    let i=0, j = 0
    let ivl = setInterval(() => {
        screen.innerHTML += args[j][i]
        i++
        if (i == args[j].length ){
            i = 0;
            j++
            screen.innerHTML += '<br>'
        }
        if (j === args.length) clearInterval(ivl)
    }, 200)
}
slowPrint(messages)
<div id="screen"></div>

您的代码出现问题的原因是for循环不会停止并等待超时。 for循环使全部超时几乎同时开始,因此1000毫秒后它们全部触发。当您需要定期进行某些操作时,setInterval通常是一种更好的方法。

当然,还有许多种其他方式可以做到这一点。只是一些更奇特的示例,这是一种使用简单生成器的方法。有点难以理解,但是一旦习惯了发电机,看起来就会很干净:

const out = document.getElementById('screen')
const messages = ["all systems are operational","you may proceed"];

function *iter(messages) {
    for(m of messages){
        for(letter of m)  yield letter
        yield '<br>'
    }
}

const gen = iter(messages)
const int = setInterval(() => {
    let n = gen.next()
    if (n.done) return clearInterval(int)
    out.innerHTML += n.value
}, 100)
<div id='screen'></div>

答案 3 :(得分:0)

从使用队列到使用数学计算范围,有无数种方法可以做到这一点。无需过多修改代码,您只需检查您是否在最后一个字符处,然后附加一个换行符并使用一个变量来跟踪输出的当前时间。

 const messages = [
  "all systems are operational",
  "you may proceed"
];

function slowPrint(args) {

  let screen = document.getElementById('screen');
  let delay = 0;
  const timeDelay = 100;
  for (let i = 0; i < args.length; i++) {
    
    
    let message = args[i];

    for (let j = 0; j < message.length; j++) {
      setTimeout(function () {
        let lineBr = j === message.length - 1 ? '<br>' : ''
        screen.innerHTML += message[j] + lineBr;
      }, delay);
      delay += timeDelay
    }

  }

}

slowPrint(messages)
<div id="screen"></div>

我个人会使用更多的队列,这样就不会创建大量的计时器。

const messages = [
  "all systems are operational",
  "you may proceed"
];

function slowPrint(args) {

  let screen = document.getElementById('screen');

  // combine all the strings into one character array 
  var characters = messages.reduce( function (a, s) {
    // turn string into an array of characters
    var letters = s.split('')
    // last character, add a line break
    var l = letters.length-1
    letters[l] = letters[l] + '<br/>'
    // append it to our current list
    return a.concat(letters);
  }, []);
  
  function next() {
    // append the first character of the array to our output
    screen.innerHTML += characters.shift()
    // if we still have more characters, than run it again
    if (characters.length) window.setTimeout(next, 100);
  }
  // kick off the script to output the characters
  next()

}

slowPrint(messages)
<div id="screen"></div>