Eine alte Frage aus dem Jahr 2009. Jetzt im Jahr 2015 ist eine neue Lösung mit Generatoren in ECMAScript 2015 AKA ES6 definiert möglich. Es wurde im Juni 2015 genehmigt, aber es wurde in Firefox und Chrome vor implementiert. Jetzt kann eine Sleep-Funktion nicht mehr blockiert und in Schleifen und Unterfunktionen verschachtelt werden, ohne dass der Browser einfriert. Es wird nur reines JavaScript benötigt - keine Bibliotheken oder Frameworks.
Das folgende Programm zeigt, wie sleep()
y runSleepyTask()
hergestellt werden können. Die sleep()
Funktion ist nur eine yield
Erklärung. Sie ist so einfach, dass es eigentlich einfacher ist, die yield
Anweisung direkt anstelle des Aufrufs sleep()
aber dann gäbe es kein sleep-Wort :-) Die Ausbeute gibt einen Zeitwert an die next()
innere Methode wakeup()
und wartet. Das eigentliche "Schlafen" findet statt in wakeup()
unter Verwendung der guten alten setTimeout()
. Beim Rückruf wird die next()
Methode löst die yield
Anweisung fortzusetzen, und die "Magie" von Yield besteht darin, dass alle lokalen Variablen und der gesamte Aufrufstapel um sie herum noch intakt sind.
Funktionen, die sleep() oder yield verwenden, müssen als Generatoren definiert werden. Dies ist einfach durch Hinzufügen eines Sternchens zum Schlüsselwort function*
. Die Ausführung eines Generators ist etwas komplizierter. Beim Aufruf mit dem Schlüsselwort new
gibt der Generator ein Objekt zurück, das die next()
Methode, aber der Körper des Generators wird nicht ausgeführt (das Schlüsselwort new
ist optional und macht keinen Unterschied). Die next()
Methode löst die Ausführung des Generatorkörpers aus, bis sie auf eine yield
. Die Wrapper-Funktion runSleepyTask()
startet das Ping-Pong-Spiel: next()
wartet auf eine yield
et yield
wartet ein next()
.
Eine andere Möglichkeit, einen Generator aufzurufen, ist mit dem Schlüsselwort yield*
Hier funktioniert es wie ein einfacher Funktionsaufruf, aber es beinhaltet auch die Möglichkeit der Rückgabe an next()
.
Dies alles wird durch das folgende Beispiel veranschaulicht drawTree()
. Es zeichnet einen Baum mit Blättern auf eine rotierende 3D-Szene. Ein Baum wird als Stamm mit 3 Teilen an der Spitze in verschiedenen Richtungen gezeichnet. Jedes Teil wird dann als ein anderer, kleinerer Baum gezeichnet, indem man drawTree()
nach einem kurzen Schlaf rekursiv. Ein sehr kleiner Baum wird nur als ein Blatt gezeichnet.
Jedes Blatt hat sein eigenes Leben in einer separaten Aufgabe, die mit runSleepyTask()
. Sie wird geboren, wächst, sitzt, verblasst, fällt und stirbt in growLeaf()
. Die Geschwindigkeit wird kontrolliert mit sleep()
. Dies zeigt, wie einfach Multitasking sein kann.
function* sleep(milliseconds) {yield milliseconds};
function runSleepyTask(task) {
(function wakeup() {
var result = task.next();
if (!result.done) setTimeout(wakeup, result.value);
})()
}
//////////////// written by Ole Middelboe /////////////////////////////
pen3D =setup3D();
var taskObject = new drawTree(pen3D.center, 5);
runSleepyTask(taskObject);
function* drawTree(root3D, size) {
if (size < 2) runSleepyTask(new growLeaf(root3D))
else {
pen3D.drawTrunk(root3D, size);
for (var p of [1, 3, 5]) {
var part3D = new pen3D.Thing;
root3D.add(part3D);
part3D.move(size).turn(p).tilt(1-p/20);
yield* sleep(50);
yield* drawTree(part3D, (0.7+p/40)*size);
}
}
}
function* growLeaf(stem3D) {
var leaf3D = pen3D.drawLeaf(stem3D);
for (var s=0;s++<15;) {yield* sleep(100); leaf3D.scale.multiplyScalar(1.1)}
yield* sleep( 1000 + 9000*Math.random() );
for (var c=0;c++<30;) {yield* sleep(200); leaf3D.skin.color.setRGB(c/30, 1-c/40, 0)}
for (var m=0;m++<90;) {yield* sleep( 50); leaf3D.turn(0.4).tilt(0.3).move(2)}
leaf3D.visible = false;
}
///////////////////////////////////////////////////////////////////////
function setup3D() {
var scene, camera, renderer, diretionalLight, pen3D;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75,
window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 15, 20);
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
directionalLight = new THREE.DirectionalLight(0xffffaa, 0.7);
directionalLight.position.set(-1, 2, 1);
scene.add(directionalLight);
scene.add(new THREE.AmbientLight(0x9999ff));
(function render() {
requestAnimationFrame(render);
// renderer.setSize( window.innerWidth, window.innerHeight );
scene.rotateY(10/60/60);
renderer.render(scene, camera);
})();
window.addEventListener(
'resize',
function(){
renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
},
false
);
pen3D = {
drawTrunk: function(root, size) {
// root.skin = skin(0.5, 0.3, 0.2);
root.add(new THREE.Mesh(new THREE.CylinderGeometry(size/12, size/10, size, 16),
root.skin).translateY(size/2));
root.add(new THREE.Mesh(new THREE.SphereGeometry(size/12, 16),
root.skin).translateY(size));
return root;
},
drawLeaf: function(stem) {
stem.skin.color.setRGB(0, 1, 0);
stem.add(new THREE.Mesh(new THREE.CylinderGeometry(0, 0.02, 0.6),
stem.skin) .rotateX(0.3).translateY(0.3));
stem.add(new THREE.Mesh(new THREE.CircleGeometry(0.2),
stem.skin) .rotateX(0.3).translateY(0.4));
return stem;
},
Thing: function() {
THREE.Object3D.call(this);
this.skin = new THREE.MeshLambertMaterial({
color: new THREE.Color(0.5, 0.3, 0.2),
vertexColors: THREE.FaceColors,
side: THREE.DoubleSide
})
}
};
pen3D.Thing.prototype = Object.create(THREE.Object3D.prototype);
pen3D.Thing.prototype.tilt = pen3D.Thing.prototype.rotateX;
pen3D.Thing.prototype.turn = pen3D.Thing.prototype.rotateY;
pen3D.Thing.prototype.move = pen3D.Thing.prototype.translateY;
pen3D.center = new pen3D.Thing;
scene.add(pen3D.center);
return pen3D;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>
Das 3D-Zeug ist in setup3D() versteckt und wird nur eingefügt, damit es weniger langweilig ist als console.log(). Winkel werden übrigens in Radiant gemessen.
Funktioniert in Firefox und Chrome. Nicht implementiert in Internet Explore und iOS (iPads). Versuchen Sie, es selbst auszuführen.
Nach einem weiteren Durchgang durch die Antworten habe ich festgestellt, dass Gabriel Ratener vor einem Jahr eine ähnliche Antwort gegeben hat zu Was ist die JavaScript-Version von sleep()? .