JavaScript代码之间延迟顺序调用的替代方法

4

我有一段最初是用Python编写的代码。

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

setTimeout() 内部嵌套 setTimeout(),会使 node.js 代码变得非常丑陋。

为了提高可读性,如何改进 node.js 代码?对于这个问题,我不关心违反 JavaScript 异步特性,重要的是易读性。


1
嵌套超时是异步“毁灭金字塔”的一种变体。另一种替代方法是使用某种形式的流:这包括承诺。 - user2864740
4个回答

5

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模块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. 使用PromiseDeferred对象

您可以创建基于Promise的超时函数。
Joe描述了一种可能的实现方式。但是,为了更容易理解它的工作原理,我提供了一个小代码片段,使用jQuery的Deferred

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

如果不支持Promise,你需要获取ECMAScript的polyfills,例如核心库core-js中的Promise或任何其他独立的Promises/A+实现组件。
Deffered也可以作为单独的NPM包Deffered获得,这个概念在这里被很好地描述了

2
你可以使用Promise:
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+,但如果需要的话,可以轻松地不使用它们。


有时候,我们需要让代码在旧版本的NodeJS上运行:这时候我们就需要为ECMAScript获取polyfills,例如从core-js包中获取Promises,或者使用独立的组件实现,比如Promises/A+实现 - Farside

0
请注意稍后运行函数的时间。
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);

5
由于所有计时器同时启动,因此与原始时间相比会存在“漂移”,特别是如果被调用的函数需要花费一定时间。 - user2864740
如果被执行的函数中有一个比您指定的延迟时间更长,怎么办?我想将其与 Promises 混合使用可能是正确的做法。 - martskins
为什么奇怪的解决方案看起来比一行代码少丑陋?我不理解。要么将它们可配置化,要么使用定时器实现一行代码,但不要过度复杂化。(参考链接:https://dev59.com/ZZXfa4cB1Zd3GeqPfXZY#36309007) - Farside

0

既然你要求提供其他方法,我也来写一个。

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

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

在任何时候,如果你需要停止执行这些命令,只需调用clearInterval(yourInterval)

如果你仍然关心可读性,你可以将迭代器放在setInterval中,并将内容包装在一个漂亮干净的函数中。祝好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接