3847 Stimmen

Was ist die JavaScript-Version von sleep()?

Gibt es einen besseren Weg zur Entwicklung einer sleep in JavaScript als die folgenden pausecomp Funktion ( entnommen von hier )?

function pausecomp(millis)
{
    var date = new Date();
    var curDate = null;
    do { curDate = new Date(); }
    while(curDate-date < millis);
}

Es handelt sich nicht um ein Duplikat von Sleep in JavaScript - Verzögerung zwischen Aktionen Ich möchte eine echter Schlaf in der Mitte einer Funktion und nicht eine Verzögerung, bevor ein Teil des Codes ausgeführt wird.

0voto

Jacob Punkte 555

Die derzeit akzeptierte Lösung der Verwendung von async/await y setTimeout ist perfekt, wenn Sie wirklich so viele Sekunden warten müssen. Wenn Sie es jedoch für Bildschirmanimationen verwenden, sollten Sie wirklich die requestAnimationFrame() . Dies funktioniert ganz ähnlich wie setTimeout pero der Callback wird nur aufgerufen, wenn die Animation für den Benutzer sichtbar ist . Das heißt, wenn Sie auf Ihrer Website eine Animation laufen lassen und der Benutzer die Registerkarte wechselt, wird die Animation angehalten und spart Batteriestrom.

Hier ist eine Implementierung des wait Methode mit requestAnimationFrame . Es nimmt eine Reihe von Bildern auf und löst sich auf, wenn alle Bilder durchgelaufen sind:

const animationWait = (frames) => 
  new Promise((resolve) => {
    let framesPassed = 0;
    requestAnimationFrame(function loop() {
      if (++framesPassed >= frames) return resolve();
      requestAnimationFrame(loop);
    });
  });

// typewriter effect for demonstration
const content = document.querySelector(".content");

async function typeWriter(endText, wait) {
  content.textContent = "";
  for (const letter of endText) {
    content.textContent += letter;
    await animationWait(wait);
  }
}

typeWriter("Okay. This simple typewriter effect is an example of requestAnimationFrame.", 8);

<p>
  The animation will play below; Try switching tabs and see that   
  the animation pauses.
</p>
<code class="content"></code>

Lesen Sie mehr über requestAnimationFrame

Browser-Unterstützung (IE10+)

0voto

smallscript Punkte 559

Ich denke, die Frage ist großartig und zeigt wichtige Perspektiven und Überlegungen auf.

Ich denke, der Kern der Frage liegt in der Absicht und im Verständnis dessen, was der Entwickler (Sie) kontrollieren möchte.

Erstens, der Name sleep ist eine überladene Namenswahl. D.h., "was" wird "geschlafen"; und "was" habe ich als Entwickler unter Kontrolle?

In jeder Sprach-Engine, die auf jedem OS-Prozess, auf jedem Bare-Metal- oder gehosteten System läuft, hat der "Entwickler" NICHT die Kontrolle (Eigentümer) der vom Betriebssystem gemeinsam genutzten CPU-Kerne [und/oder Threads], es sei denn, sie schreiben das Betriebssystem/Prozesssystem selbst. CPUs sind eine zeitlich geteilte Ressource, und die Währung des Arbeitsfortschritts sind die "Zyklen", die allen im System auszuführenden Arbeiten zugewiesen werden.

Als Entwickler einer Anwendung/eines Dienstes ist es am besten, wenn ich mir vorstelle, dass ich die Kontrolle über einen Workflow-Aktivitätsstrom habe, der von einem OS-Prozess/einer Sprach-Engine verwaltet wird. Auf einigen Systemen bedeutet das, dass ich einen nativen Os-Thread kontrolliere (die sich wahrscheinlich CPU-Kerne teilen) auf andere bedeutet es, dass ich eine asynchroner Fortsetzungs-Workflow Kette/Baum .

Im Falle von JavaScript ist es das "Letztere".

Wenn ich also "schlafen" möchte, möchte ich, dass mein Arbeitsablauf eine gewisse Zeit lang nicht ausgeführt wird, bevor er den nächsten Schritt (Phase/Aktivität/Aufgabe) in seinem Arbeitsablauf ausführt.

Dies bedeutet "passenderweise", dass es für einen Entwickler am einfachsten ist, wenn er (Denken Sie in Begriffen von) die Arbeit als linearen Codefluss zu modellieren und auf Kompositionen von Arbeitsabläufen zurückzugreifen, um sie nach Bedarf zu skalieren.

In JavaScript haben wir heute die Möglichkeit, solche linearen Arbeitsabläufe mit effizienten Multitasking-Architekturen aus den 1980er Jahren zu gestalten. (umbenannt in moderne Zukünfte/Versprechen/dann/erwarten usw.) .

In diesem Sinne lautet meine Antwort no einen Beitrag leisten neu technische Lösung, sondern vielmehr die Konzentration auf die Absicht und die Entwurf Perspektive in der Frage selbst.

Ich schlage vor, dass jede Antwort damit beginnt, über die oben genannten Konzepte nachzudenken und dann einen NAMEN zu wählen (außer sleep ), die daran erinnern und darauf hinweisen, was die Absicht ist.

Arbeitsablauf

  • Wahlmöglichkeit 1: delayWorkForMs(nMsToDelay)
  • Wahlmöglichkeit 2: delayAsyncSequenceForMs(msPeriod)
async delayAsyncSequenceForMs(msPeriod) {
  await new Promise(resolve => setTimeout(resolve, msPeriod));
}

Denken Sie daran, dass jede async Funktion immer gibt eine Promise et await darf nur innerhalb einer async Funktion .
(lol, Sie fragen sich vielleicht, warum...) .

  • Erwägung 1: nicht "Schleifen" verwenden, um CPU-Zyklen zu VERBRENNEN.

  • Überlegung 2: Im JavaScript-Modell können Sie innerhalb einer nicht asynchronen Funktion die Ausführung eines "asynchronen" Arbeitsablaufs nicht "verzögern" (auf ihn warten). (es sei denn, Sie tun schlimme Dinge, die unnötig Rechenzeit verbrauchen) . Sie können Code-Schritte nur innerhalb einer "asynchronen" Funktion "verzögern".
    Intern wird eine "async"-Funktion als eine Sammlung von Einstiegspunkten/Fortsetzungen an jedem der folgenden Punkte modelliert await Stichwort . Wenn Sie mit dem Backtick-Interpolationsmodell vertraut sind, können Sie sich "await" ähnlich vorstellen wie das Schreiben eines Backquote-Strings:

  // Conceptualizing, using an interpolation example to illustrate
  // how to think about "await" and "async" functions
  `code${await then-restart-point}more-code${await then-restart-point}`

0voto

PlugTrade Punkte 742

Ich bin mir sicher, dass es eine Million Möglichkeiten gibt, dies besser zu machen, aber ich dachte, ich versuche es mal mit der Erstellung eines Objekts:

// execute code consecutively with delays (blocking/non-blocking internally)
function timed_functions()
{
    this.myfuncs = [];
    this.myfuncs_delays = []; // mirrors keys of myfuncs -- values stored are custom delays, or -1 for use default
    this.myfuncs_count = 0; // increment by 1 whenever we add a function
    this.myfuncs_prev    = -1; // previous index in array
    this.myfuncs_cur    = 0; // current index in array
    this.myfuncs_next  = 0; // next index in array
    this.delay_cur     = 0; // current delay in ms
    this.delay_default = 0; // default delay in ms
    this.loop = false;      // will this object continue to execute when at end of myfuncs array?
    this.finished = false;  // are we there yet?
    this.blocking = true;   // wait till code completes before firing timer?
    this.destroy = false;   // <advanced> destroy self when finished

    this.next_cycle = function() {
        var that  = this;
        var mytimer = this.delay_default;

        if(this.myfuncs_cur > -1)
            if(this.myfuncs_delays[this.myfuncs_cur] > -1)
                mytimer = this.myfuncs_delays[this.myfuncs_cur];

        console.log("fnc:" + this.myfuncs_cur);
        console.log("timer:" + mytimer);
        console.log("custom delay:" + this.myfuncs_delays[this.myfuncs_cur]);
        setTimeout(function() {
        // Time is up! Next cycle...
        that.cycle();

    }, mytimer);
}

this.cycle = function() {

    // Now check how far we are along our queue.. is this the last function?
    if(this.myfuncs_next + 1 > this.myfuncs_count)
    {
        if(this.loop)
        {
            console.log('looping..');
            this.myfuncs_next = 0;
        }
        else
            this.finished = true;
    }

    // First check if object isn't finished
    if(this.finished)
        return false;

    // HANDLE NON BLOCKING //
    if(this.blocking != true) // Blocking disabled
    {
        console.log("NOT BLOCKING");
        this.next_cycle();
    }

    // Set prev = current, and current to next, and next to new next
    this.myfuncs_prev = this.myfuncs_cur;
    this.myfuncs_cur  = this.myfuncs_next;
    this.myfuncs_next++;

    // Execute current slot
    this.myfuncs[this.myfuncs_cur]();

    // HANDLE BLOCKING
    if(this.blocking == true)  // Blocking enabled
    {
        console.log("BLOCKING");
        this.next_cycle();
    }

    return true;
};

// Adders
this.add = {
    that:this,

    fnc: function(aFunction) {
        // Add to the function array
        var cur_key = this.that.myfuncs_count++;
        this.that.myfuncs[cur_key] = aFunction;
        // Add to the delay reference array
        this.that.myfuncs_delays[cur_key] = -1;
    }
}; // end::this.add

// setters
this.set = {
    that:this,

    delay: function(ms) {
        var cur_key = this.that.myfuncs_count - 1;
        // This will handle the custom delay array this.that.myfunc_delays
        // Add a custom delay to your function container

        console.log("setting custom delay. key: "+ cur_key + " msecs: " + ms);
        if(cur_key > -1)
        {
            this.that.myfuncs_delays[cur_key] = ms;
        }
        // So now we create an entry on the delay variable
    },

    delay_cur:      function(ms) { this.that.delay_cur = ms; },
    delay_default:  function(ms) { this.that.delay_default = ms; },
    loop_on:        function()   { this.that.loop = true; },
    loop_off:       function()   { this.that.loop = false; },
    blocking_on:    function()   { this.that.blocking = true; },
    blocking_off:   function()   { this.that.blocking = false; },

    finished:            function(aBool) { this.that.finished = true; }
}; // end::this.set

// Setters
this.get = {
    that:this,

    delay_default: function() { return this.that.delay_default; },
    delay_cur:     function() { return this.that.delay_cur; }
    }; // end::this.get

} // end:::function timed_functions()

Und verwenden Sie es wie:

// // // BEGIN :: TEST // // //

// Initialize
var fncTimer = new timed_functions;

// Set some defaults
fncTimer.set.delay_default(1000);
fncTimer.set.blocking_on();
// fncTimer.set.loop_on();
// fncTimer.set.loop_off();

// BEGIN :: ADD FUNCTIONS (they will fire off in order)
fncTimer.add.fnc(function() {
    console.log('plan a (2 secs)');
});
fncTimer.set.delay(2000); // Set a custom delay for previously added function

fncTimer.add.fnc(function() {
    console.log('hello world (delay 3 seconds)');
});
fncTimer.set.delay(3000);

fncTimer.add.fnc(function() {
    console.log('wait 4 seconds...');
});
fncTimer.set.delay(4000);

fncTimer.add.fnc(function() {
    console.log('wait 2 seconds');
});
fncTimer.set.delay(2000);

fncTimer.add.fnc(function() {
    console.log('finished.');
});
// END :: ADD FUNCTIONS

// NOW RUN
fncTimer.cycle(); // Begin execution

// // // END :: TEST // // //

0voto

Caty Lawren Punkte 1

Si usted wirklich ein Skript pausieren möchten, können Sie dies tun:

var milliseconds;
var pretime;
var stage;

function step(time){
  switch(stage){
    case 0:
      //Code before the pause

      pretime=time;
      milliseconds=XXX;
      stage=1;
      break;
    case 1:
      //Code that is looped through while paused

      if(time-pretime >= milliseconds){
        //Code after the pause

        pretime=time;
        milliseconds=XXX;
        stage=2;
      }
      break;
    case 2:
      //Code that is looped through while paused

      if(time-pretime >= milliseconds){
        //Code after the pause

        pretime=time;
        milliseconds=XXX;
        stage=3;
      }
      break;
    case 3:
      //Etc...
  }

  Window.requestAnimationFrame(step)
}

step();

Das ist wahrscheinlich genau das, was Sie wollen, wenn Sie sowieso eine Schleife verwenden, und Sie können es so ändern, dass Sie Pseudo-Multi-Threading haben, bei dem einige Funktionen eine Weile warten und andere normal laufen. Ich verwende dies die ganze Zeit für reine JavaScript-Spiele.

0voto

cscan Punkte 194

Ich habe mich einen Tag lang mit den Lösungen beschäftigt, aber ich denke immer noch darüber nach, wie ich die Verkettbarkeit bei der Verwendung eines Rückrufs aufrechterhalten kann.

Jeder ist mit dem traditionellen Programmierstil vertraut, bei dem der Code Zeile für Zeile synchronisiert wird. SetTimeout verwendet einen Rückruf, so dass die nächste Zeile nicht auf ihre Fertigstellung wartet. Dies lässt mich darüber nachdenken, wie man es "synchronisieren" kann, um eine "Sleep"-Funktion zu erstellen.

Beginnend mit einer einfachen Coroutine:

function coroutine() {
    console.log('coroutine-1:start');
    sleepFor(3000); // Sleep for 3 seconds here
    console.log('coroutine-2:complete');
}

Ich möchte in der Mitte 3 Sekunden schlafen, aber ich möchte nicht den gesamten Ablauf dominieren, also muss die Coroutine von einem anderen Thread ausgeführt werden. Ich betrachte die Unity YieldInstruction und ändern Sie die Koroutine wie folgt:

function coroutine1() {
    this.a = 100;
    console.log('coroutine1-1:start');
    return sleepFor(3000).yield; // Sleep for 3 seconds here
    console.log('coroutine1-2:complete');
    this.a++;
}

var c1 = new coroutine1();

Deklarieren Sie den sleepFor-Prototyp:

sleepFor = function(ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?sleepFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    setTimeout(function() {
        new Function(funcArgs, funcBody).apply(context, args);
    }, ms);
    return this;
}

Nach dem Ausführen der Coroutine1 (ich habe sie in Internet Explorer 11 und Chrome 49 getestet), werden Sie sehen, dass sie zwischen zwei Konsolenanweisungen 3 Sekunden schläft. Es hält die Codes so schön wie der traditionelle Stil.

Das Knifflige liegt in der sleepFor-Routine. Sie liest den Körper der aufrufenden Funktion als String und zerlegt ihn in zwei Teile. Der obere Teil wird entfernt und mit dem unteren Teil eine weitere Funktion erstellt. Nachdem sie die angegebene Anzahl von Millisekunden gewartet hat, ruft sie die erstellte Funktion unter Verwendung des ursprünglichen Kontexts und der ursprünglichen Argumente auf. Der ursprüngliche Ablauf wird wie üblich mit "return" beendet. Für das "yield"? Es wird für den Abgleich mit regulären Ausdrücken verwendet. Er ist notwendig, aber nicht von Nutzen.

Sie ist nicht zu 100 % perfekt, aber sie erfüllt zumindest meine Aufgaben. Ich muss einige Einschränkungen bei der Verwendung dieses Codes erwähnen. Da der Code in zwei Teile aufgeteilt ist, muss die "return"-Anweisung in der äußeren Anweisung stehen, anstatt in einer Schleife oder {}. d.h.

function coroutine3() {
    this.a = 100;
    console.log('coroutine3-1:start');
    if(true) {
        return sleepFor(3000).yield;
    } // <- Raise an exception here
    console.log('coroutine3-2:complete');
    this.a++;
}

Der obige Code muss ein Problem haben, da die schließende Klammer nicht einzeln in der erstellten Funktion existieren kann. Eine weitere Einschränkung ist, dass alle lokalen Variablen, die mit "var xxx=123" deklariert werden, nicht in die nächste Funktion übernommen werden können. Sie müssen "this.xxx=123" verwenden, um das Gleiche zu erreichen. Wenn Ihre Funktion Argumente hat und diese geändert werden, kann der geänderte Wert auch nicht in die nächste Funktion übertragen werden.

function coroutine4(x) { // Assume x=abc
    var z = x;
    x = 'def';
    console.log('coroutine4-1:start' + z + x); // z=abc, x=def
    return sleepFor(3000).yield;
    console.log('coroutine4-2:' + z + x); // z=undefined, x=abc
}

Ich würde einen weiteren Funktionsprototyp einführen: waitFor

waitFor = function(check, ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?waitFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    var thread = setInterval(function() {
        if(check()) {
            clearInterval(thread);
            new Function(funcArgs, funcBody).apply(context, args);
        }
    }, ms?ms:100);
    return this;
}

Es wartet auf die Funktion "check", bis sie true zurückgibt. Sie prüft den Wert alle 100 ms. Sie können sie durch Übergabe eines zusätzlichen Arguments anpassen. Betrachten Sie die Test-Coroutine2:

function coroutine2(c) {
    /* Some code here */
    this.a = 1;
    console.log('coroutine2-1:' + this.a++);
    return sleepFor(500).yield;

    /* Next */
    console.log('coroutine2-2:' + this.a++);
    console.log('coroutine2-2:waitFor c.a>100:' + c.a);
    return waitFor(function() {
        return c.a>100;
    }).yield;

    /* The rest of the code */
    console.log('coroutine2-3:' + this.a++);
}

Auch in dem hübschen Stil, den wir bisher so lieben. Eigentlich hasse ich den verschachtelten Callback. Es ist leicht verständlich, dass die Coroutine2 auf den Abschluss von Coroutine1 wartet. Interessant? Ok, dann führen Sie den folgenden Code aus:

this.a = 10;
console.log('outer-1:' + this.a++);
var c1 = new coroutine1();
var c2 = new coroutine2(c1);
console.log('outer-2:' + this.a++);

Die Ausgabe ist:

outer-1:10
coroutine1-1:start
coroutine2-1:1
outer-2:11
coroutine2-2:2
coroutine2-2:waitFor c.a>100:100
coroutine1-2:complete
coroutine2-3:3

Outer wird unmittelbar nach der Initialisierung von coroutine1 und coroutine2 abgeschlossen. Dann wartet Coroutine1 3000 ms lang. Coroutine2 beginnt nach einer Wartezeit von 500 ms mit Schritt 2. Danach fährt sie mit Schritt 3 fort, sobald sie feststellt, dass die Werte von coroutine1.a > 100 sind.

Beachten Sie, dass es drei Kontexte für die Variable "a" gibt. Einer ist outer, die Werte sind 10 und 11. Ein anderer ist in coroutine1, die Werte sind 100 und 101. Der letzte ist in coroutine2, die Werte sind 1, 2 und 3. In der Coroutine2 wird auch auf c.a aus der Coroutine1 gewartet, bis der Wert größer als 100 ist. Die 3 Kontexte sind unabhängig.

Der gesamte Code für Copy&Paste:

sleepFor = function(ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?sleepFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    setTimeout(function() {
        new Function(funcArgs, funcBody).apply(context, args);
    }, ms);
    return this;
}

waitFor = function(check, ms) {
    var caller = arguments.callee.caller.toString();
    var funcArgs = /\(([\s\S]*?)\)/gi.exec(caller)[1];
    var args = arguments.callee.caller.arguments;
    var funcBody = caller.replace(/^[\s\S]*?waitFor[\s\S]*?yield;|}[\s;]*$/g,'');
    var context = this;
    var thread = setInterval(function() {
        if(check()) {
            clearInterval(thread);
            new Function(funcArgs, funcBody).apply(context, args);
        }
    }, ms?ms:100);
    return this;
}

function coroutine1() {
    this.a = 100;
    console.log('coroutine1-1:start');
    return sleepFor(3000).yield;
    console.log('coroutine1-2:complete');
    this.a++;
}

function coroutine2(c) {
    /* Some code here */
    this.a = 1;
    console.log('coroutine2-1:' + this.a++);
    return sleepFor(500).yield;

    /* next */
    console.log('coroutine2-2:' + this.a++);
    console.log('coroutine2-2:waitFor c.a>100:' + c.a);
    return waitFor(function() {
        return c.a>100;
    }).yield;

    /* The rest of the code */
    console.log('coroutine2-3:' + this.a++);
}

this.a = 10;
console.log('outer-1:' + this.a++);
var c1 = new coroutine1();
var c2 = new coroutine2(c1);
console.log('outer-2:' + this.a++);

Sie wurde in Internet Explorer 11 und Chrome 49 getestet. Weil es verwendet argumente.calllee Daher kann es zu Problemen kommen, wenn es im Strict-Modus läuft.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X