如何按顺序执行定时任务?

时间:2016-12-29 00:04:54

标签: javascript

如何使用Javascript按顺序运行可变数量的定时任务?

我正在为免费代码营中的经典电子记忆游戏Simon进行娱乐活动。我使用Web Audio API生成声音,我需要正确计时。

我需要start一个声音,等待420毫秒,stop声音,然后在开始下一个声音之前等待500毫秒。

当我只需要播放单个声音时,我可以使用setTimeout使其正常工作,但我无法弄清楚如何让它等待播放后续声音。

这是显示问题的代码的子集。它可以正常使用1个声音,但是当它不止一个时,它会同时播放所有声音(除了第一个声音?):



var audioContext = new AudioContext();
var oscillator = null;

function playSound(frequency) {
  oscillator = audioContext.createOscillator();
  oscillator.type = 'square';
  oscillator.connect(audioContext.destination);
  oscillator.frequency.value = frequency;
  oscillator.start();
}

function stopSound() {
  oscillator.stop();
  oscillator.disconnect(audioContext.destination);
  oscillator = null;
}

var frequencies =  [329.628, 220, 277.183, 164.814];

var turn = 1;
var soundCounter = 0;

var duration = 0;
if (turn <= 5) {
  duration = 420;
} else if (turn <= 13) {
  duration = 320;
} else {
  duration = 220;
}

for (var i = 0; i < turn; i++) {
  var freqId = Math.floor(Math.random() * frequencies.length);
  var frequency = frequencies[freqId];
  playSound(frequency);
  setTimeout(function() { stopSound(); }, duration);
}
&#13;
&#13;
&#13;

谢谢。

更新:完成了!

如果没有@RyanZim,@ trincot和@ lee-daniel-crocker提供的优秀答案,我无法完成这项工作。这些答案都不能直接用于copy&amp;粘贴,这是一件好事,因为我应该学习一些东西:D我从所有答案中学习并使用了一些东西。因此,需要有一种方法可以为这种情况选择多个正确的答案。

好奇? http://codepen.io/VAggrippino/pen/WoBQXR

代码在某些地方很邋,,但它确实有效,我现在宁愿继续前进而不是完美。

4 个答案:

答案 0 :(得分:2)

您需要使用递归函数。这是一个有效的例子:

var audioContext = new AudioContext();
var oscillator = null;

function playSound(frequency) {
  oscillator = audioContext.createOscillator();
  oscillator.type = 'square';
  oscillator.connect(audioContext.destination);
  oscillator.frequency.value = frequency;
  oscillator.start();
}

function stopSound() {
  oscillator.stop();
  oscillator.disconnect(audioContext.destination);
  oscillator = null;
}

var frequencies =  [329.628, 220, 277.183, 164.814];

var turn = 5;
var soundCounter = 0;

var duration = 0;
if (turn <= 5) {
  duration = 420;
} else if (turn <= 13) {
  duration = 320;
} else {
  duration = 220;
}

playSeq();
function playSeq(i) {
  if (!i) i = 0;
  var freqId = Math.floor(Math.random() * frequencies.length);
  var frequency = frequencies[freqId];
  playSound(frequency);
  setTimeout(function() {
    stopSound();
    i++;
    if (i < turn) playSeq(i);
  }, duration);
}

for循环在这里不起作用,因为循环将继续到下一次迭代,而不等待setTimeout回调被执行。一个简单的例子:

for (var i = 0; i < 3; i++) {
  // Schedules the code to run in the future:
  setTimeout(function () { console.log('Hi'); }, 2);
  // Continues to the next line:
  console.log(i);
  // Continues to the next iteration of the loop...
}

通过使用递归函数,js引擎将等待setTimeout代码完成以继续下一次迭代。

无论何时使用setTimeoutsetInterval,如果要等待超时完成,就不能依赖JS循环,必须使用递归函数。

答案 1 :(得分:1)

您需要异步循环,但您还需要存储生成的音符,以便您可以为游戏重复这些音符。

我还建议使用一个承诺,所以你知道回放何时结束。

这是一个工作片段(首先关闭你的音量):

var audioContext = new AudioContext();
var oscillator = null;

function playSound(frequency) {
  oscillator = audioContext.createOscillator();
  oscillator.type = 'square';
  oscillator.connect(audioContext.destination);
  oscillator.frequency.value = frequency;
  oscillator.start();
}

function stopSound() {
  oscillator.stop();
  oscillator.disconnect(audioContext.destination);
  oscillator = null;
}

var frequencies =  [329.628, 220, 277.183, 164.814];
// You need to store the tones that were already generated, so
// the sequence remains the same once generated:
var sequence = [];

function playTurn(turn) {
    return new Promise(function (resolve) {
        // First complete the sequence:
        while (sequence.length < turn) {
            sequence.push(Math.floor(Math.random() * frequencies.length));
        }    

        var duration = turn <=  5 ? 420
                     : turn <= 13 ? 320
                     :              220;

        (function loop(i) {
            if (i >= sequence.length) return resolve();
            playSound(frequencies[sequence[i]]);
            setTimeout(stopSound, duration);
            setTimeout(loop.bind(null,i+1), duration*2.2);
        })(0);
    });
}

// Generate and play 4 notes:
playTurn(4).then(function() {
    alert('done');
});

答案 2 :(得分:1)

以下是我可能会这样做的(在ES6中,您可能需要翻译/加载承诺库等):

// Using playSound(), stopSound() from question

let furElise = [
    [ 329.63, 200 ],
    [ 311.13, 200 ],
    [ 329.63, 200 ],
    [ 311.13, 200 ],
    [ 329.63, 200 ],
    [ 246.94, 200 ],
    [ 293.66, 200 ],
    [ 261.63, 200 ],
    [ 220.00, 400 ]
];

function playNote(pitch, duration) {
    return new Promise((resolve, reject) => {
        playSound(pitch);
        setTimeout(() => {
            stopSound();
            resolve(true);
        }, duration);
    });
}

function playTune(notes) {
    if (notes.length > 0) {
        playNote(notes[0][0], notes[0][1]).then(() => {
            playTune(notes.slice(1));
        });
    }
}

playTune(furElise);

答案 3 :(得分:0)

(代表问题作者发布的解决方案,将其移至答案空间)

我已经完成了!

如果没有@ RyanZim,@ trincot和@ lee-daniel-crocker提供的出色答案,我将无法完成。没有一个答案可直接用于复制和粘贴,这是一件好事,因为我应该学习的是:D我从所有答案中学习并使用了一些答案。

代码如下:

/* jshint esversion: 6 */
var Game = function() {
  var audioContext = new window.AudioContext();
  var oscillator = null;
  var gainNode = null;
  var winner = false;

  var that = this;
  this.power = false;
  this.started = false;
  this.playerTurn = false;
  this.strictMode = false;
  this.sequence = [];
  this.playerSequence = [];
  this.turn = 0;

  var winningSong = [];
  winningSong.push($('.button.red').get(0));
  winningSong.push($('.button.yellow').get(0));
  winningSong.push($('.button.blue').get(0));
  winningSong.push($('.button.green').get(0));
  winningSong.push($('.button.green').get(0));
  winningSong.push($('.button.green').get(0));
  winningSong.push($('.button.red').get(0));
  winningSong.push($('.button.yellow').get(0));

  var playSound = function(frequency) {
    if (!that.power || !that.started || oscillator !== null) return false;
    gainNode = audioContext.createGain();
    oscillator = audioContext.createOscillator();
    oscillator.type = 'square';
    oscillator.frequency.value = frequency;

    oscillator.connect(gainNode);
    gainNode.connect(audioContext.destination);

    gainNode.gain.value = 0.1;

    oscillator.start();
  };

  var stopSound = function() {
    if (!oscillator || !gainNode) return false;
    oscillator.stop();
    gainNode.disconnect(audioContext.destination);

    oscillator = null;
    gainNode = null;
  };

  var randomButton = function() {
    var $buttons = $('.button');

    var buttonNumber = Math.floor(Math.random() * $buttons.length);
    return $buttons[buttonNumber];
  };

  var playTone = function(frequency, duration) {
    return new Promise((resolve, reject) => {
      playSound(frequency);
      setTimeout(() => {
        stopSound();
        resolve(true);
      }, duration);
    });
  };

  var playSequence = function(buttons) {
    if (buttons.length === 0) {
      that.playerTurn = true;
      if (winner) {
        that.start();
      }
      return that.playerTurn;
    }

    // Assign the duration of the tone based on how many
    // turns we've already played.
    // ref: http://www.waitingforfriday.com/?p=586#Sound_frequencies_and_timing
    var duration = that.sequence.length <= 5  ? 420
                 : that.sequence.length <= 13 ? 320
                 :                              220;

    if (winner) {
      duration = 100;
    }

    var button = buttons[0];
    if (!winner) {
      if (buttons.length === that.sequence.length) console.log('+++++');
      console.log((that.sequence.length - buttons.length) + ':' + $(button).data('color'));
      if (buttons.length === 1) console.log('-----');
    }

    that.activateButton(button);

    var frequency = $(button).data('frequency');
    playTone(frequency, duration).then(() => {
      that.deactivateButton(button);
      playSequence(buttons.slice(1));
    });
  };

  var takeTurn = function() {
    if ( that.turn === 20 ) {
      winner = true;
      playSequence(winningSong);
      return true;
    }
    that.turn++;

    // Add a new button to the sequence.
    that.sequence.push(randomButton());

    // If necessary, prepend the turn number with a zero
    // before updating the display.
    var displayString = that.turn < 10 ? '0' + that.turn : '' + that.turn;
    $('.display').html(displayString);

    playSequence(that.sequence);
  };

  this.activateButton = function(button) {
    // If the game hasn't been turned on or started, don't do
    // anything.
    if (!that.power || !that.started) return false;

    // Create a jQuery object from the DOM element reference.
    var $button = $(button);

    // Light up the active button.
    $button.addClass('active');
  };

  this.deactivateButton = function(e) {
    var $button = $(e.target || e);
    stopSound();
    $button.removeClass('active');

    if (that.playerTurn && that.playerSequence.length === that.sequence.length) {
      that.playerTurn = false;
      that.playerSequence = [];
      setTimeout(takeTurn, 500);
    }
  };

  this.pressButton = function(e) {
    if (!that.power || !that.started || !that.playerTurn) return false;

    // Add the pressed button to the player's button sequence.
    that.playerSequence.push(e.target);
    that.activateButton(e.target);

    playSound($(e.target).data('frequency'));

    // Check if the player's button matches the computer's
    // button at the same position in the sequence.
    var playerButton = e.target;
    var computerButton = that.sequence[that.playerSequence.length - 1];

    // If the player's button doesn't match, play the
    // failure sound and end the game.
    // ref: http://www.waitingforfriday.com/?p=586#Sound_frequencies_and_timing
    if (playerButton !== computerButton) {
      that.playerTurn = false;
      that.playerSequence = [];
      setTimeout(function() {
        that.deactivateButton(e.target);
        stopSound();
        playSound(42);
        if (that.strictMode) {
          that.started = false;
          setTimeout(stopSound, 1500);
        } else {
          setTimeout(function() {
            stopSound();
            playSequence(that.sequence);
          }, 500);
        }
      }, 200);
    }
  };

  this.start = function() {
    if (!that.power) return false;
    winner = false;
    that.started = true;
    that.turn = 0;
    $('.display').html('00');
    that.sequence = [];
    that.playerSequence = [];
    takeTurn();
  };

  this.toggleStrict = function() {
    if (!that.power) return false;
    that.strictMode = !that.strictMode;
    $('.strict').toggleClass('on');
  }
};

$(function() {
  // Assign the tone frequency based on the button's color.
  $('div.button.green').data('frequency', '329.628');
  $('div.button.red').data('frequency', '220');
  $('div.button.yellow').data('frequency', '277.183');
  $('div.button.blue').data('frequency', '164.814');

  /*
  // From http://www.waitingforfriday.com/?p=586#Sound_frequencies_and_timing
  $buttons.filter('.green').data('frequency', '415');
  $buttons.filter('.red').data('frequency', '310');
  $buttons.filter('.yellow').data('frequency', '252');
  $buttons.filter('.blue').data('frequency', '209');

  // From Wikipedia: "Simon (game)"
  $buttons.filter('.green').data('frequency', '659.255');
  $buttons.filter('.red').data('frequency', '440');
  $buttons.filter('.yellow').data('frequency', '277.18');
  $buttons.filter('.blue').data('frequency', '329.628');

  // From the Free Code Camp forums:
  https://forum.freecodecamp.com/t/better-audiocontext-frequencies-for-simon/69483/2?u=vaggrippino
  */

  var $buttons = $('div.button');

  var game = new Game();
  $buttons.on('mousedown', game.pressButton);
  $buttons.on('mouseup mouseleave', game.deactivateButton);

  $('.power .switch').on('click', function() {
    game.power = !game.power;
    $('.power .switch').toggleClass('on');
    $('.display').html(game.power ? '--' : '');
  });

  $('.start').on('click',  game.start);
  $('.strict').on('click', game.toggleStrict);
});

http://codepen.io/VAggrippino/pen/WoBQXR

该代码在某些地方草率,但它可以工作,我宁愿继续而不是立即完善它。