Старый вопрос с 2009 года. Теперь в 2015 году возможно новое решение с генераторами, определенными в ECMAscript 2015, а также ES6. Он был одобрен в июне, но раньше он был реализован в Firefox и Chrome. Теперь функция сна может быть сделана не занятой, неблокирующей и вложенной внутренней петлей и подфункций без замораживания браузера. Требуется только чистый JavaScript, нет библиотек или фреймворков.
В приведенной ниже программе показано, как сделать sleep()
и runSleepyTask()
. Функция sleep()
- это только оператор yield
. Это так просто, что на самом деле легче написать оператор yield
непосредственно вместо вызова sleep()
, но тогда не будет слова сна :-). Результат возвращает значение времени для метода next()
внутри wakeup()
и ждет. Фактический «спящий» выполняется в wakeup()
, используя старый добрый setTimeout()
. При обратном вызове метод next()
запускает оператор yield
для продолжения, а «магия» доходности заключается в том, что все локальные переменные и весь стек вызовов вокруг него все еще остаются нетронутыми.
Функции которые используют sleep () или yield должны быть определены как генераторы. Легко сделать, добавив звездочку к ключевому слову function*
. Выполнять генератор немного сложнее. При вызове с ключевым словом new
генератор возвращает объект, который имеет метод next()
, но тело генератора не выполняется (ключевое слово new
является необязательным и не имеет значения). Метод next()
запускает выполнение тела генератора до тех пор, пока он не встретит yield
. Функция обертки runSleepyTask()
запускает пинг-понг: next()
ждет yield
, а yield
ждет next()
.
Другой способ вызвать генератор - с ключевым словом yield*
, здесь он работает как простой вызов функции, но также включает способность возвращаться к next()
.
Все это демонстрируется примером drawTree()
. Он рисует дерево с листьями на вращающейся 3D-сцене. Дерево рисуется как ствол с тремя частями вверху в разных направлениях. Затем каждая часть рисуется как другое, но меньшее дерево, вызывая drawTree()
рекурсивно после короткого сна. Очень маленькое дерево рисуется как только лист.
Каждый лист имеет свою собственную жизнь в отдельной задаче, начатой с runSleepyTask()
. Он рождается, растет, сидит, исчезает, падает и умирает growLeaf()
. Скорость регулируется с помощью sleep()
. Это демонстрирует, как легко выполнить многозадачность.
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;
}
3D-материал скрыт внутри setup3D () и включен только в сделать его менее скучным, чем console.log ().
Проверено на работу в Firefox и Chrome. Не реализовано в Internet Explore и iOS (iPads). Попробуйте запустить его самостоятельно.
После очередного ответа, который я нашел, Габриэль Ратенер сделал аналогичный ответ год назад: https://stackoverflow.com/a/24401317/5032384
Этот метод устарел. Посмотрите на обновление ниже.
Вместо services.AddTransient<IUrlHelper, UrlHelper>()
или для непосредственного ввода IUrlHelper вы можете ввести IHttpContextAccessor и получить оттуда службу.
public ClassConstructor(IHttpContextAccessor contextAccessor)
{
this.urlHelper = contextAccessor.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();
}
Если это не просто ошибка , добавление службы IUrlHelper с помощью UrlHelper не работает.
UPDATE 2017-08-28
Предыдущий метод больше не работает. Ниже представлено новое решение.
Confgure IActionContextAccessor как служба:
public void ConfigureServices(IServiceCollection services)
{
services
.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddMvc();
}
Затем введите IActionContextAccessor и IUrlHelperFactory, чтобы затем сгенерировать IUrlHelper, как показано ниже
public class MainController : Controller
{
private IUrlHelperFactory urlHelperFactory { get; }
private IActionContextAccessor accessor { get; }
public MainController(IUrlHelperFactory urlHelper, IActionContextAccessor accessor)
{
this.urlHelperFactory = urlHelper;
this.accessor = accessor;
}
[HttpGet]
public IActionResult Index()
{
ActionContext context = this.accessor.ActionContext;
IUrlHelper urlHelper = this.urlHelperFactory.GetUrlHelper(context);
//Use urlHelper here
return this.Ok();
}
}
UrlHelper требует текущего контекста действий, и мы можем получить его из ActionContextAccessor. Я использую это:
services.AddScoped<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(x =>
{
var actionContext = x.GetService<IActionContextAccessor>().ActionContext;
return new UrlHelper(actionContext);
});
Теперь вы можете вводить IUrlHelper непосредственно во все, что ему нужно, не перепрыгивая через IHttpContextAccessor.
ASP.NET Core 2.0
Установить
PM> Install-Package AspNetCore.IServiceCollection.AddIUrlHelper
Использовать
public void ConfigureServices(IServiceCollection services)
{
...
services.AddUrlHelper();
...
}
Отказ от ответственности: автор этого пакета