使用 JavaScript 回调函数来管理队列。

10

我正在处理一个使用JavaScript管理队列的页面。我的挑战是我的代码包含嵌套的回调函数。这些嵌套的回调函数让我对队列的作用域感到困惑。目前,我的代码如下:

function MyApp() {}
module.exports = MyApp;

MyApp.myQueue = [];
MyApp.queueIsLocked = false;

MyApp.enqueue = function(item, onSuccess, onFailure) {
  if (!MyApp.queueIsLocked) {
    MyApp.queueIsLocked = true;        
    MyApp.myQueue.push(item);
    MyApp.queueIsLocked = false;

    item.send(   
      function() {
        console.log('item: ' + item.id);

        MyApp.queueIsLocked = true;                      
        MyApp.findItemById(item.id,
          function(index) {
            if (index !== -1) {
              MyApp.myQueue.splice(index, 1);
              MyApp.queueIsLocked = false;

              if (onSuccess) {
                onSuccess(item.id);
              }
            }
          }
        );
      },
      function() {
        alert('Unable to send item to the server.');
        if (onFailure) {
          onFailure();
        }
      }
    );
  }
};

MyApp.findItemById = function(id, onComplete) {
  var index = -1;
  if (MyApp.queueIsLocked) {
    setTimeout(function() {
      // Attempt to find the index again.
    }, 100);
  } else {
    MyApp.queueIsLocked = true;
    for (var i=0; i<MyApp.myQueue.length; i++) {
      if (MyApp.myQueue[i].id === id) {
        index = i;
        break;
      }
    }
  }

  if (onComplete) {
    onComplete(index);
  }
};

send函数根据item的细节表现不同。有时项目将被发送到一个服务器,有时将被发送到多个服务器。无论哪种方式,我都不知道什么时候项目将完成“发送”。因此,我使用回调来管理队列。当项目完成“发送”时,我想将其从队列中删除。我需要使用超时或间隔来检查队列是否已锁定。如果没有被锁定,我想从队列中删除该项。这个检查添加了另一层嵌套,让我感到困惑。

我的挑战是,我认为索引的范围不像我预期的那样工作。我觉得我遇到了竞争条件。我基于以下Jasmine测试编写:

describe('Queue', function() {
  describe('Approach 1', function() {
    it('should do something', function() {
      MyApp.enqueue({id:'QRA', text:'Test A'});
    });
  });

  describe('Approach 2', function() {
    it('should successfully queue and dequeue items', function() {
      MyApp.enqueue({id:'WX1', text:'Test 1'});
      MyApp.enqueue({id:'QV2', text:'Test 2'});
      MyApp.enqueue({id:'ZE3', text:'Test 3'});
    });
  });
});
当我执行这个测试时,在控制台窗口中看到以下内容:
item: QRA index: 1
item: WX1 index: 2
item: QV2 index: 3
item: ZE3 index: 4

就像这些项目没有按照我预期的出队。我的队列管理方式是不是错了?我做错了什么?

感谢任何帮助。


你为什么需要检查队列是否被锁定?我在你的代码中没有看到任何必要的地方。 - slebetman
@slebetman 更新了代码。非常抱歉。 - user70192
已经在我的回答中添加了代码。希望能有所帮助。 - GreenGiant
4个回答

10

这是一些关于您意图和设计需要思考和自己回答的问题:

  1. 听起来队列代表您正在尝试发送到服务器的项目。您将需要发送的项目添加到队列中,在它们成功发送后从队列中删除。
  2. 您是否希望您的代码同时并行发送多个项目?例如,将项目A添加到队列中,然后发送。在异步发送A完成之前,将项目B添加到列表中。代码是否应该在项目A发送完成之前尝试发送项目B?根据您的代码,听起来是可以的。

似乎您并不需要一个真正的队列,而更像是想要一个列表来跟踪哪些项目正在被发送。 "Queue" 暗示对象正在以某种 FIFO 顺序进行处理。

如果您只想根据 id 跟踪项目,则可以使用一个对象代替。例如:

MyApp.items = {};
MyApp.addItem = function(item){
  MyApp.items[item.id] = item;
  item.send(
    function(){ // success
      MyApp.removeItem(item.id)
    }
  );
}
MyApp.removeItem = function(id){
  delete MyApp.items[id];
  onSuccess(id);
}

另外,我认为你不需要在队列上加锁。Javascript是单线程的,因此您永远不会遇到两段代码同时尝试操作队列的情况。当一个异步调用完成时,您的回调代码实际上只有在任何当前正在执行的代码完成后才会被执行。


3
我看到的一个大问题是你在调用MyApp.findItemById之前立即调用了MyApp.queueIsLocked = true。因为它被锁定,函数设置了一个超时(什么也没做),并继续调用onComplete(-1)-1然后被onComplete明确忽略,未能出队,从而锁定了你的队列。
你可能想要像这样重新尝试查找:
setTimeout(function() {
  // Attempt to find the index again.
  MyApp.findItemById(id, onComplete);
}, 100);

我不确定,但我认为Jasmine需要明确的指令才能触发超时函数,使用jasmine.clock().tick
话虽如此,我建议删除所有对queueIsLocked的引用,包括上面的超时代码。另外,如果item.id始终是唯一的字符串,则可以使用对象而不是数组来存储您的值。
这里提供了一个建议的迭代,尽可能忠实于原始API:
function MyApp() {}
module.exports = MyApp;

MyApp.myQueue = {};

//Sends the item, calling onSuccess or onFailure when finished
//  item will appear in MyApp.myQueue while waiting for a response from send
MyApp.enqueue = function(item, onSuccess, onFailure) {
  MyApp.myQueue[item.id] = item;

  item.send(function() {
    console.log('item: ' + item.id);
    delete MyApp.myQueue[item.id];
    if (onSuccess) {
      onSuccess(item.id);
    }
  }, function() {
    alert('Unable to send item to the server.');
    if (onFailure) {
      onFailure();
    }
  });
};

//Returns the Item in the queue, or undefined if not found
MyApp.findItemById = function(id, onComplete) {
  if (onComplete) {
    onComplete(id);
  }
  return MyApp.myQueue[id];
};

2

尝试使用ECMA 6 Promise或js框架中的任何Promise。Promise更适合这个任务。更多信息请参见https://developer.mozilla.org/

function MyApp() {}
module.exports = MyApp;

MyApp.myQueue = [];
MyApp.queueIsLocked = false;

MyApp.enqueue = function(item) {
 return new Promise(function(resolve, reject) {
      if (!MyApp.queueIsLocked) {
        MyApp.queueIsLocked = true;        
        MyApp.myQueue.push(item);
        MyApp.queueIsLocked = false;

        var onResolve = function() {
            console.log('item: ' + item.id);
            MyApp.queueIsLocked = true;   
            MyApp.findItemById(item.id).then(function(index){
                 if (index !== -1) {
                      MyApp.myQueue.splice(index, 1);
                      MyApp.queueIsLocked = false;
                      resolve(item.id);
                 }
            });     

        };

        item.send(onResolve,reject);
      }
  });
};

MyApp.findItemById = function(id) {
     return new Promise(function(resolve, reject) {
              var index = -1;
              if (MyApp.queueIsLocked) {
                setTimeout(function() {
                  // Attempt to find the index again.
                }, 100);
              } else {
                MyApp.queueIsLocked = true;
                for (var i=0; i<MyApp.myQueue.length; i++) {
                  if (MyApp.myQueue[i].id === id) {
                    index = i;
                    break;
                  }
                }
                resolve(index);
              }
        });
};

0
MyApp.queueIsLocked = false; 移动到服务器发送的回调函数中。

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