依次运行不同的setTimeouts

时间:2018-08-05 15:28:08

标签: javascript asynchronous foreach settimeout

我有一系列不同的“否”。分钟”,我想一个接一个地倒计时:

const steps = [{
    label: "Label1",
    time: 4
  },
  {
    label: "Label2",
    time: 2
  },
  {
    label: "Label3",
    time: 1
  }
];

function countdownTimer(time) {

  setTimeout(function () {

    console.log("DONE");

  }, parseInt(time + "000"));
}

function start() {

  steps.forEach(function (step) {
    countdownTimer(step.time);
  });

}

但是,与setTimeout的性质一样,它们似乎都在同时运行,并且最短的时间首先显示。

如何使setTimeout依次运行它们,即显示4、2,然后显示1?

这是codepen:

https://codepen.io/aguerrero/pen/yqqMVX

5 个答案:

答案 0 :(得分:2)

您的函数应在开始倒数计时之前等待倒数计时器完成,因此forEach将不起作用。 forEach仅在将上一个对象的时间添加到当前计时器时才起作用,这似乎有点过多。只需等待每个计时器完成,然后再开始下一个计时器。为此使用回调:

function countdownTimer(obj, callback) {
    setTimeout(function(){ 
        console.log(obj.label + ": DONE");
        callback();                                   // when the current cound down is done call callback to start the next one
    }, parseInt(obj.time + "000"));
}

var start = (function () {                            // wrapping in an IIFE to not pollute the global scope with the index variable. You can get rid of it if you want
    var index = 0;                                    // since we are not using a regular loop nor forEach, we need an index to keep track of the current step
    return function next() {                          // this function is called for each step
        if(index < steps.length) {                    // it check if there still a step in the array steps
            countdownTimer(steps[index], next);       // if so it starts its count down timer, telling it to call next once it finished
            index++;
        }
    };
})();

示例:

const steps = [ { label: "Label1",  time: 4 }, { label: "Label2", time: 2 }, { label: "Label3", time: 1 } ];

function countdownTimer(obj, callback) {
    setTimeout(function(){ 
        console.log(obj.label + ": DONE");
        callback();
    }, parseInt(obj.time + "000"));
}

var start = (function () {
    var index = 0;
    return function next() {
        if(index < steps.length) {
            countdownTimer(steps[index], next);
            index++;
        }
    };
})();

console.log("Starting...");
start();

答案 1 :(得分:1)

您可以让计时器在完成后调用该函数,并为下一个索引传递参数:

const steps = [{label: "Label1",time: 4},{label: "Label2",time: 2},{label: "Label3",time: 1}];

function countdownTimer(i) {
  if (i >= steps.length) return 
  setTimeout(function() {
    console.log("DONE WITH", steps[i].label);
    countdownTimer(i + 1)
  }, steps[i].time * 1000);    
}

countdownTimer(0)

如果您需要从循环中调用countdownTimer,则可以跟踪累积的时间并将其添加到下一个调用中:

const steps = [{label: "Label1",time: 4},{label: "Label2",time: 2},{label: "Label3",time: 1}];

function countdownTimer(time) {
  setTimeout(function() {
    console.log("DONE");
  }, time * 1000);
}

function start() {
  let time = 0                         // keep track of time
  steps.forEach(function(step) {
    countdownTimer(step.time + time);  // add it to call
    time += step.time                  // increase for next call
  }); 
}
start()

答案 2 :(得分:1)

如果您想使用现代的ES6异步/等待方式,可以使用以下代码段

const steps = [
	{
		label: "Label1",
		time: 4
	},
	{
		label: "Label2",
		time: 2
	},
	{
		label: "Label3",
		time: 1
	}
];

const wait = (ms) => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(ms), ms)
    });
};

const start = async () => {
    const startTime = performance.now();

    // use ES6 iterator of the steps array and loop (iterate) over it
    // destructuring is used to obtain index and step
    // for loop is used since .forEach is not awaiting for the callback
    for (const [index, step] of steps.entries()) {
        // await the callback of the wait function
        // and console.log resolved statement
        console.log(`${step.label} done in ${await wait(step.time * 1000)} ms`);
    }

    console.log(`Done in ${performance.now() - startTime}`);
};

start();

我使用的是for循环,而不是forEach,因为最后一个没有等待回调。

答案 3 :(得分:0)

这是一种实现方式

 const output = document.getElementById("result");
 const steps = [
    {
        label: "Label1",
        time: 1
    },
    {
        label: "Label2",
        time: 2
    },
    {
        label: "Label3",
        time: 3
    }
];
const processStep = (step) => {
  return new Promise((resolve, reject) => {
    if (!step) { resolve(false); }
    let timeout = parseInt(step.time + "000");
    setTimeout(() => { 
        console.log("DONE", timeout);
        output.innerHTML = "Step " + step.label + " completed after " + step.time + " seconds";
        resolve(step);
    }, timeout);
  });
};
const processSteps = (steps) => {
  let currentStep = steps.shift();
  if (!currentStep) { return ; }
  console.log("processing step", currentStep);
  processStep(currentStep).then(() => {
    processSteps(steps);
  });
};

processSteps(steps);
 <section id="result"></section>

答案 4 :(得分:0)

我们可以使用async await

解决此问题

const steps = [{
    label: "Label1",
    time: 4
  },
  {
    label: "Label2",
    time: 2
  },
  {
    label: "Label3",
    time: 1
  }
];

// I added `label` parameter to make it easier to see it works
function countdownTimer(time, label) {

  // need to make it as promise function for `await` to work later
  return new Promise((resolve, reject) => {
      setTimeout(function () {

        console.log(`${label} DONE`);
        resolve(); // resolve the promise

      }, parseInt(time + "000"));
  });
}

// specify async because we use await inside the loop
async function start() {
  for (const step of steps) {
    await countdownTimer(step.time, step.label);
  };
}

start();