4

I have this code originally in python.

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

I converted this code to node.js but the code looks much uglier.

SendSerialPortCommand("XXX");

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

Imagine if my python code looks like this.

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

The node.js code will look really ugly with setTimeout() inside setTimeout().

How can the node.js code be improved in terms of readability? I don't care about violating asynchronous nature of javascript for this question. Important thing is readability.

guagay_wk
  • 26,337
  • 54
  • 186
  • 295
  • 1
    The nested timeouts is a variation of the asynchronous ['pyramid of doom'](https://spin.atomicobject.com/2012/03/14/nodejs-and-asynchronous-programming-with-promises/). One alternative approach is to use some form of streams: this includes promises. – user2864740 Mar 27 '16 at 01:15

4 Answers4

5

1. One-liner solution:

Previously accepted solution just complicates the things, and not brings any readability or improvement. Do it like this then, just one-liners:

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

2. Simple configurable solution:

If you want to make it configurable, move options to the config above, and call in the loop, alike:

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);
}

Here's the JSFiddle, to play with.

3. Using node module node-fibers

If you want advanced solution through node.js to "show off", you may go node-fibers way, and to create sleep function, alike in their manual.

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 implemenation is being used in tons of other smaller libraries, alike WaitFor. More information could be found here.

4. Using Promise & Deferred Objects

You can create a Promise based timeout function. Joe described one of possible implementations. But I will provide small code snippet, for easier understanding on how it actually works, using Defferred from 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");
});

If promises are not supported, you will need to get polyfills for ECMAScript, for example Promises from core-js package or any other standalone component of Promises/A+ implementation.

Deffered, might be gotten as separate Deffered package for NPM as well, the concept is nicely described here.

Community
  • 1
  • 1
Farside
  • 9,923
  • 4
  • 47
  • 60
2

You could use promises:

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"));

Or, including the delay into the 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+ is required for using arrow functions, but this can be done without them easily, if needed.

Joe Krill
  • 1,734
  • 1
  • 18
  • 19
  • one might need to make it work on old versions of NodeJS: then one would need to get *polyfills* for *ECMAScript*, for example [Promises from **core-js** package](https://www.npmjs.com/package/core-js#ecmascript-6-promise) or as standalone component implementation, e.g. [Promises/A+ implementation](https://github.com/kevincennis/promise) – Farside Mar 31 '16 at 09:00
0

Take note of the timings in running the functions later.

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);
guagay_wk
  • 26,337
  • 54
  • 186
  • 295
  • 5
    Since all the timers are started at once this will 'drift' compared to the original, especially if the called function takes relevant time. – user2864740 Mar 27 '16 at 01:13
  • what if one of the functions being executed takes longer than the delay diff you specified? I guess mixing this with promises would be the right thing to do – martskins Mar 30 '16 at 12:14
  • strange solution, why it's less uglier, than one-liners?! Not getting this. Make [them configurable, or do one-liners with Timers](http://stackoverflow.com/a/36309007/4354249), without over-complication. – Farside Mar 30 '16 at 12:35
0

Since you asked for alternative ways I'll write one as well.

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

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

At any point you need to stop the execution of those commands you just call clearInterval(yourInterval)

If you're still concerned with readability you could enclose the iterator inside the setInterval and wrap contents in a nice clean function. Good luck!