Saya memiliki fungsi foo dan saya ingin menambahkan fungsi sleep/wait untuk membuat semacam animasi elemen DOM. Saya sudah melakukan riset dan saya tahu bahwa tidak mungkin untuk menjeda fungsi javascript karena itu membekukan browser - perbaiki saya jika saya salah. Bagaimana saya bisa mengatasinya?

function foo() {     
 while (someCondition) {
  var $someDiv = $('.someDiv:nth-child(' + guess + ')');
  $someDiv.css({'background-color': 'red'});
  wait 1000ms
  $someDiv.css({'background-color': 'blue'});
  wait 1000ms
  if (someCondition2) { 
   doSomething; }
  else {
   for loop }
 }
}

$someDiv merujuk ke elemen DOM yang berbeda dengan setiap iterasi loop while karena variabel guess berubah

Yang telah saya coba

  • Saya menggunakan fungsi di bawah ini dan berhasil tetapi masalahnya adalah saya tidak dapat menggunakan loop for di foo fungsi async saya

    function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
    }
    
  • Saya mencoba setTimeout tetapi saya tidak dapat mencapai hasil yang valid.

    Jika saya membungkus setTimeout potongan kode ini: ('$someDiv').css({'background-color': 'red'}); kemudian setelah jumlah waktu yang ditentukan, semua $someDiv's mengubah gaya css bersama-sama (perlu diingat bahwa $someDiv merujuk ke elemen DOM yang berbeda dengan setiap iterasi loop while).

    Jika saya membungkus setTimeout sepotong kode dengan pernyataan if, else maka saya mendapatkan kesalahan - Infinite Loop

Pertanyaan

Fungsi foo disederhanakan hanya untuk visualisasi masalah. Fungsi asli yang sedang saya kerjakan dapat Anda temukan di codepen (findNumber fungsi )

Saya ingin membuat animasi algoritma pencarian biner. Sesuatu yang mirip dengan ini

Bagaimana saya bisa mencapai hasil yang diinginkan?

Secara umum: Bagaimana saya bisa menganimasikan elemen DOM dalam satu lingkaran dengan interval di antara setiap iterasi?

6
Arkej 22 Desember 2016, 14:59

5 jawaban

Jawaban Terbaik

Solusi terbaik dan terbersih untuk masalah ini adalah dengan async/await fitur yang akan hadir dalam versi Javascript (ES2017) mendatang. Ini memungkinkan Anda untuk keluar dari neraka panggilan balik. Anda dapat membuat fungsi sleep sederhana yang terlihat seperti ini:

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

Anda dapat menggunakan ini dengan penanganan Promise normal:

sleep(1000).then(()=>console.log('A second later'));

Namun, dengan fungsi async Anda dapat menggunakan kata kunci await untuk membuat kode menunggu janji diselesaikan sebelum melanjutkan.

async function doSomething() {
  await sleep(1000);
  console.log('A second later');
}

Ini berarti Anda juga dapat menggunakan loop normal, termasuk pernyataan break dan continue:

async function doSomething() {
  let i = 0;
  while (true) {
    await sleep(1000);
    console.log(i);
    if (++i === 5) break;
  }
}

Ini berarti bahwa kode Anda dapat disederhanakan secara dramatis:

async function foo() {
  var n = 5;
  while (n > 0) {
    n--;
    var wait = 0;
    //$('#someDiv').css({'background-color': 'red'});
    console.log('doSomething-1');
    //wait 1000ms
    await sleep(1000);
    //$('#someDiv').css({'background-color': 'blue'});
    console.log('doSomething-2');

    //wait 1000ms
    await sleep(1000);
    if (true) {
      console.log('doSomething-3');
      break;
    } else {
      console.log('loop')
    }
  }
}

(jsFiddle)

Satu-satunya masalah adalah fungsi ini jauh dari dukungan universal. Oleh karena itu, Anda perlu mengubahnya menggunakan perangkat lunak seperti Babel.

Perhatikan juga bahwa, di balik layar, fungsi foo Anda sekarang segera kembali dan memberikan Promise. Promise itu diselesaikan dengan nilai kembalian fungsi. Jadi jika Anda ingin melakukan lebih banyak kode ketika foo selesai, Anda harus melakukan foo().then(/*callback*/).

2
lonesomeday 22 Desember 2016, 13:40

Saya mengoreksi kode Andre Rodrigues :

var nbCall = 0;

function foo() {
    var wait = 0;
    $('#someDiv').css({'background-color': 'red'});

    //wait 1000ms
    wait = wait + 1000;
    setTimeout(function(){
        $('#someDiv').css({'background-color': 'blue'});
    }, wait);

    //wait 2000ms
    wait = wait + 1000;
    setTimeout(function(){
        if (nbCall++ > 5) { // Are other cond
             console.log('doSomething');
        }
        else {
            foo();
        }
    }, wait);
}
foo();

JsFiddle : https://jsfiddle.net/nnf8vcko/

0
Fefux 22 Desember 2016, 13:15

Saya membuat kode sederhana ... mungkin dapat membantu Anda:

function foo() {
    while (true) {
        var wait = 0;
        $('#someDiv').css({'background-color': 'red'});

        //wait 1000ms
        wait = wait + 1000;
        setTimeout(function(){
            $('#someDiv').css({'background-color': 'blue'});
        }, wait);

        //wait 1000ms
        wait = wait + 1000;
        setTimeout(function(){
            if (true) { 
            console.log('doSomething');
            }
            else {
            console.log('loop')
            }
        }, wait);
        break;
    }
}
foo();

Rahasianya ada di "tunggu", nilai ini akan menjadi jumlah waktu yang Anda inginkan, dengan nilai "tunggu" terakhir. Anda akan mendapatkan hasil yang diharapkan tetapi pendekatan lain.

Saya harap saya telah membantu.

0
Andre Rodrigues 22 Desember 2016, 12:21

Anda dapat melakukannya dengan Menggunakan jQuery queue(), dequeue(), delay( )

Tetapi karena Anda memiliki semacam penutupan di dalam loop, Anda harus gunakan bind() untuk mengikat variabel per iterasi ke dalam fungsi antrian .

Di sini ada antrian untuk body dan masing-masing satu antrian untuk setiap guess, Antrian setiap tebakan didequeue dalam body dequeues. Jenis loop di dalam loop. IMHO mungkin ada cara yang lebih mudah, ini hanya untuk memberikan gambaran tentang antrian, penundaan, dan pengikatan.

Saya telah membuat beberapa perubahan kecil pada kode Anda, hanya untuk membuat demo berfungsi.

$(document).ready(function() {
  var body = $('body');
  //buttons click handler functions
  $('#generateButton').on("click", generateArray);
  $('#findButton').on("click", findNumber);
  //enable calling functions by 'enter' key
  $('#generateInput').keypress(function(e) {
    if (e.which == 13) {
      generateArray();
    }
  });
  $('#findInput').keypress(function(e) {
    if (e.which == 13) {
      findNumber();
    }
  });

  //functions
  function generateArray() {
    //variables
    var $generateGroup = $('.generate');
    var $generateInput = $('#generateInput');
    var $generateInputVal = $generateInput.val();
    var $generateButton = $('#generateButton');
    var $findInput = $('#findInput');
    var $findButton = $('#findButton');
    var $arraySection = $('.array-section');
    //validation
    if ($.isNumeric($generateInputVal) && $generateInputVal >= 10 && $generateInputVal <= 100) {
      //set styling if success
      $generateGroup.removeClass('has-error');
      $generateButton.removeClass('error');
      $generateGroup.addClass('has-success');
      $generateButton.addClass('success');
      //disable generate input group
      $generateInput.prop('disabled', true);
      $generateButton.prop('disabled', true);
      //enable find input group
      $findInput.prop('disabled', false);
      $findButton.prop('disabled', false);
      //clear array section
      $arraySection.empty();
      //generate array = create divs and append them to array section
      for (var i = 0; i < $generateInputVal; i++) {
        var $number = $('<div>', {
          'class': 'number'
        });
        $arraySection.append($number.text(i + 1));
      }
    } else {
      // set styling if error
      $generateGroup.removeClass('has-success');
      $generateButton.removeClass('success');
      $generateGroup.addClass('has-error');
      $generateButton.addClass('error');
    }
  }




  function findNumber() {
    //variables
    var animationSpeed = 5000;
    var animationCut = 1000;
    var $generateInput = $('#generateInput');
    var $generateInputVal = $generateInput.val();
    var $findInput = $('#findInput');
    var $findInputVal = $findInput.val();
    var min = 0;
    var max = parseInt($generateInputVal);
    var guess;
    var n = 0;
    var guesses = [];
    var rejected;
    // --- binary search loop ---
    while (max >= min) {
      n++;
      //compute guess as the average of max and min
      guess = Math.ceil((min + max) / 2);
      console.log("GUESS",guess);
      //guessed number animation
      var $guessNumber = $('.number:nth-child(' + guess + ')');
      console.log(min + ' ' + max + ' ' + guess);
      $guessNumber.queue('guessNum', function(next) {
        $(this).css({
          'background-color': '#000000',
          'color': '#ffffff',
          'font-weight': 'bold'
        });
        next();
      });
      $guessNumber.delay(animationCut, 'guessNum');
      //await sleep(animationSpeed);
      //var myVar = setInterval(function(){
      $guessNumber.queue('guessNum', function(next) {
        $(this).css({
          'background-color': 'white',
          'color': 'black',
          'border': '3px solid #000000'
        });
        next()
      });
      $guessNumber.delay(animationCut, 'guessNum');
      
      body.queue('guessNumbers', function(){
        console.log('guessNumbers');
        $(this).dequeue('guessNum');
        body.dequeue('guessNumbers');
      }.bind($guessNumber));
      //await sleep(animationSpeed);
      //if guessed number equals find number then stop
      if (guess === parseInt($findInputVal)) {
        //found number animation
        $guessNumber.queue('guessNum', function(next) {
          console.log('GOT RESULT', this);
          $(this).css({
            'color': '#3c763d',
            'background-color': '#dff0d8',
            'border': '3px solid #3c763d'
          });
          next();
        });
        body.dequeue('guessNumbers');
        break;
      }
      //if guessed nsumber is to low, set new min value
      else if (guess < parseInt($findInputVal, 10)) {
        
        rejected = $('.number').slice(min, guess);
        min = guess + 1;
      }
      //if guessed number is to high, set new max value
      else if(guess > parseInt($findInputVal, 10)) {
        rejected = $('.number').slice(guess, max);
        max = guess - 1;
      }
      body.queue('guessNumbers',function(){
        console.log("rejected",rejected);
        this.css({backgroundColor: 'red'});
        body.dequeue('guessNumbers');
      }.bind(rejected)).delay(animationSpeed, 'guessNumbers');
    }
  }
});

//   function sleep(ms) {
//   return new Promise(resolve => setTimeout(resolve, ms));
// }
html,
body {
  margin: 0 auto;
  box-sizing: border-box;
}
.section {
  margin-top: 40px;
  margin-bottom: 40px;
}
.array-section {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
}
.input-group {
  margin: 5px;
}
.number {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 40px;
  height: 40px;
  text-align: center;
  margin: 5px;
  padding: 5px;
  border: 1px solid gray;
  border-radius: 3px;
  transition: all 0.8s;
}
.error {
  background: rgb(202, 60, 60);
  color: white;
  border: 1px solid rgb(202, 60, 60);
  transition: 0.5s;
}
.success {
  background: rgb(28, 184, 65);
  color: white;
  border: 1px solid rgb(28, 184, 65);
  transition: 0.5s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container text-center">
  <div class="section section-title">
    <h1>BINARY SEARCH</h1>
  </div>
  <div class="section input-section">
    <div class="col-md-4 col-md-offset-2 col-sm-6 col-sm-offset-3 col-xs-8 col-xs-offset-2">
      <div class="input-group generate">
        <input type="text" class="form-control input-lg" placeholder="10 - 100" id="generateInput">
        <div class="input-group-btn">
          <button class="btn btn-default btn-lg" id="generateButton">
            Generate array
          </button>
        </div>
      </div>

    </div>
    <div class="col-md-4 col-md-offset-1 col-sm-6 col-sm-offset-3 col-xs-8 col-xs-offset-2">
      <div class="input-group">
        <input type="text" class="form-control input-lg" placeholder="1 - 100" id="findInput" disabled>
        <div class="input-group-btn">
          <button class="btn btn-default btn-lg" type="submit" id="findButton" disabled>
            Find number
          </button>
        </div>
      </div>
    </div>
  </div>

  <div class="col-xs-12 section array-section">
    <div class="number">1</div>
    <div class="number">2</div>
    <div class="number">3</div>
    <div class="number">...</div>
    <div class="number">n</div>
  </div>
</div>
1
Community 23 Mei 2017, 11:53

Anda mungkin ingin memeriksa jQuery queue() (biola):

$("div#someDiv")
  .queue(function() {
    console.log('step 1');
    $(this).css({
      'background-color': 'blue'
    }).dequeue();
  })
  .delay(800)
  .queue(function() {
    console.log('step 1');
    $(this).css({
      'background-color': 'red'
    }).dequeue();
  })

Anda juga bisa bermain-main dengan Settimeout (biola):

var steps = [
  function() {
    $('#someDiv').css({
      'background-color': 'red'
    });
  },
  function() {
    $('#someDiv').css({
      'background-color': 'orange'
    });
  },
  function() {
    $('#someDiv').css({
      'background-color': 'yellow'
    });
  },
  function() {
    $('#someDiv').css({
      'background-color': 'green'
    });
  },
  function() {
    $('#someDiv').css({
      'background-color': 'blue'
    });
  }
];


(function(count) {
  if (count < 5) {
    steps[count]();
    var caller = arguments.callee;
    window.setTimeout(function() {
      caller(count + 1);
    }, 1000);
  }
})(0);
2
user286089 22 Desember 2016, 13:46