Альтернативные способы последовательного вызова кода javascript с задержками между ними

У меня есть этот код изначально на питоне.

SendSerialPortCommand("XXX")
time.delay(0.5)
SendSerialPortCommand("YYY")

Я преобразовал этот код в node.js, но код выглядит намного уродливее.

SendSerialPortCommand("XXX");

setTimeout(function () {
    SendSerialPortCommand("YYY");
}, 500);

Представьте, если бы мой код Python выглядел так.

SendSerialPortCommand("XXX")
time.delay(0.5)
SendSerialPortCommand("YYY")
time.delay(0.5)
SendSerialPortCommand("AAA")
time.delay(0.5)
SendSerialPortCommand("BBB")

Код node.js будет выглядеть очень уродливо с setTimeout() внутри setTimeout().

Как улучшить читабельность кода node.js? Меня не волнует нарушение асинхронного характера javascript для этого вопроса. Важный момент — читабельность.


person user781486    schedule 27.03.2016    source источник
comment
Вложенные тайм-ауты — это вариант асинхронного 'пирамида судьбы'. Один из альтернативных подходов — использовать некоторую форму потоков: сюда входят промисы.   -  person user2864740    schedule 27.03.2016


Ответы (4)


1. Однострочное решение:

Ранее принятое решение просто усложняет ситуацию и не обеспечивает удобочитаемости или улучшения. Сделайте это так, только одними строками:

setTimeout(function(){ SendSerialPortCommand("XXX"); }, 500);
setTimeout(function(){ SendSerialPortCommand("YYY"); }, 1500);
setTimeout(function(){ SendSerialPortCommand("ZZZ"); }, 2000);

2. Простое настраиваемое решение:

Если вы хотите сделать его настраиваемым, переместите параметры в конфигурацию выше и вызовите в цикле аналогично:

var schedulerData = [
   {delay: 500,  params: "XXX"},
   {delay: 1500, params: "YYY"},
   {delay: 2000, params: "ZZZ"}
];

for (var i in schedulerData) {
    var doTimeout = function(param, delay) {
        setTimeout(function(){ SendSerialPortCommand(param); }, delay );
    };
    doTimeout(schedulerData[i].params, schedulerData[i].delay);
}

Вот JSFiddle, с которым можно поиграть.

3. Использование модуля узла node-fibers

Если вы хотите, чтобы продвинутое решение через node.js «показывалось», вы можете перейти на node-fibers и создать функцию sleep, как в их руководстве.

var Fiber = require('fibers');

function sleep(ms) {
    var fiber = Fiber.current;
    setTimeout(function() {
        fiber.run();
    }, ms);
    Fiber.yield();
}

Fiber(function() {
    SendSerialPortCommand("XXX");
    sleep(1000);
    SendSerialPortCommand("YYY");
}).run();
console.log('still executing the main thread');

node-fibers используется во множестве других меньших библиотек, таких как WaitFor. Дополнительную информацию можно найти здесь.

4. Использование объектов Promise и Deferred

Вы можете создать функцию тайм-аута на основе Promise. Джо описал одну из возможных реализаций. Но я приведу небольшой фрагмент кода, чтобы было легче понять, как это работает на самом деле, используя Defferred из jQuery:

function wait(ms) {
    var deferred = $.Deferred();
    setTimeout(deferred.resolve, ms);
    // We just need to return the promise not the whole deferred.
    return deferred.promise();
}

// Use it
wait(500).then(function () {
    SendSerialPortCommand("XXX");
}).wait(500).then(function () {
    SendSerialPortCommand("YYY");
});

Если промисы не поддерживаются, вам потребуется полифиллы для ECMAScript, например Promises из пакета core-js или любого другого автономного компонента реализации Promises/A+.

Отложенный, может быть получен как отдельный пакет Deffered для Кроме того, концепция NPM хорошо описана здесь.

person Farside    schedule 30.03.2016

Вы можете использовать обещания:

function Delay(duration) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(), duration);
  });
}

function SendSerialPortCommand(command) {
  // Code that actually sends the command goes here...
  console.log(command);
  return Promise.resolve();
}


Promise.resolve()
  .then(() => SendSerialPortCommand("XXX"))
  .then(() => Delay(500))
  .then(() => SendSerialPortCommand("YYY"))
  .then(() => Delay(500))
  .then(() => SendSerialPortCommand("AAA"))
  .then(() => Delay(500))
  .then(() => SendSerialPortCommand("BBB"));

Или, включая задержку в SendSerialPortCommand:

function SendSerialPortCommand(command, duration) {
  return new Promise((resolve) => {
    setTimeout(() => {
      // Code that actually sends the command goes here...
      resolve();
    }, duration);
  });
}

Promise.resolve()
  .then(() => SendSerialPortCommand("XXX", 500))
  .then(() => SendSerialPortCommand("YYY", 500))
  .then(() => SendSerialPortCommand("AAA", 500))
  .then(() => SendSerialPortCommand("BBB", 500));

Для использования стрелочных функций требуется Node 4+, но при необходимости это можно легко сделать и без них.

person Joe Krill    schedule 30.03.2016
comment
может потребоваться заставить его работать на старых версиях NodeJS: тогда нужно будет получить polyfills для ECMAScript, например, Promises из пакета core-js или как реализация отдельного компонента, например Промисы/реализация A+ - person Farside; 31.03.2016

Обратите внимание на тайминги при запуске функций позже.

var scheduler = (function(){
    var timer;
    function exec(call, delay){
        //clearTimeout(timer);
        timer = setTimeout(call, delay);
    };
    return exec;
})()

SendSerialPortCommand("XXX");
scheduler(function(){SendSerialPortCommand("YYY")}, 500);
scheduler(function(){SendSerialPortCommand("AAA")}, 1000);
scheduler(function(){SendSerialPortCommand("BBB")}, 1500);
person user781486    schedule 27.03.2016
comment
Поскольку все таймеры запускаются одновременно, это будет «дрейфовать» по сравнению с оригиналом, особенно если вызываемая функция занимает соответствующее время. - person user2864740; 27.03.2016
comment
что, если одна из выполняемых функций занимает больше времени, чем указанная вами разница задержки? Я думаю, что смешивание этого с обещаниями было бы правильным решением. - person martskins; 30.03.2016
comment
странное решение, почему оно менее уродливо, чем остроты?! Не понял. Сделайте их настраиваемыми или используйте однострочники с таймерами без чрезмерного усложнения. - person Farside; 30.03.2016

Поскольку вы попросили альтернативные способы, я тоже напишу один.

var commandIterator = 0;
var portCommands = [
   'YYY',
   'AAA'
];
SendSerialPortCommand(portCommands[commandIterator++])

var yourInterval = setInterval(function(){
   SendSerialPortCommand(portCommands[commandIterator++])
}, 500);

В любой момент вам нужно остановить выполнение тех команд, которые вы просто вызываете clearInterval(yourInterval)

Если вас все еще беспокоит удобочитаемость, вы можете заключить итератор внутри setInterval и обернуть содержимое в красивую чистую функцию. Удачи!

person TheAlmightyOne    schedule 30.03.2016