通过回调管理 JavaScript 中的队列
Managing a queue in JavaScript via callbacks
我正在处理使用 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
的细节表现不同。有时,该项目将被发送到一台服务器。有时,它会被发送到多个服务器。无论哪种方式,我不知道 知道项目什么时候完成"sent"。出于这个原因,我使用回调来管理队列。当项目完成 "sent" 后,我想将其从队列中删除。我需要使用超时或间隔来检查队列是否被锁定。如果它没有锁定,我想从队列中删除该项目。此检查添加了另一层嵌套,这让我感到困惑。
我的挑战是,我不相信索引的范围会像我预期的那样工作。我觉得我遇到了比赛条件。我的依据是我编写了以下 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'});
});
});
});
当我执行测试时,我在控制台中看到以下内容window:
item: QRA index: 1
item: WX1 index: 2
item: QV2 index: 3
item: ZE3 index: 4
好像项目没有像我预期的那样出队。我在管理队列的方法上是否偏离了基础?我做错了什么?
感谢您的帮助。
关于您的意图和设计,您需要思考并自己回答以下一些问题:
- 听起来队列代表您正试图发送到服务器的项目。您正在将需要发送的项目添加到队列中,并在成功发送后将其从队列中移除。
- 您希望您的代码同时并行发送多个项目吗?例如,项目 A 被添加到队列中,然后被发送。在 A 的异步发送完成之前,项目 B 被添加到列表中。代码是否应该在项目 A 发送完成之前尝试发送项目 B?根据您的代码,听起来是的。
看起来您并不是真正的 want/need 队列,就其本身而言,您需要一个列表来跟踪正在发送的项目。 "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 是单线程的,因此您永远不会遇到代码的两部分同时尝试对队列进行操作的情况。当 ajax 调用异步完成时,您的回调代码将不会真正执行,直到当前正在执行的任何其他代码完成。
我看到的最大缺陷是您在 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];
};
尝试使用 ECMA 6 Promise 或来自 js framework.Promiseis 的任何更适合此任务的承诺。在 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);
}
});
};
将MyApp.queueIsLocked = false;
移动到服务器发送的回调
我正在处理使用 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
的细节表现不同。有时,该项目将被发送到一台服务器。有时,它会被发送到多个服务器。无论哪种方式,我不知道 知道项目什么时候完成"sent"。出于这个原因,我使用回调来管理队列。当项目完成 "sent" 后,我想将其从队列中删除。我需要使用超时或间隔来检查队列是否被锁定。如果它没有锁定,我想从队列中删除该项目。此检查添加了另一层嵌套,这让我感到困惑。
我的挑战是,我不相信索引的范围会像我预期的那样工作。我觉得我遇到了比赛条件。我的依据是我编写了以下 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'});
});
});
});
当我执行测试时,我在控制台中看到以下内容window:
item: QRA index: 1
item: WX1 index: 2
item: QV2 index: 3
item: ZE3 index: 4
好像项目没有像我预期的那样出队。我在管理队列的方法上是否偏离了基础?我做错了什么?
感谢您的帮助。
关于您的意图和设计,您需要思考并自己回答以下一些问题:
- 听起来队列代表您正试图发送到服务器的项目。您正在将需要发送的项目添加到队列中,并在成功发送后将其从队列中移除。
- 您希望您的代码同时并行发送多个项目吗?例如,项目 A 被添加到队列中,然后被发送。在 A 的异步发送完成之前,项目 B 被添加到列表中。代码是否应该在项目 A 发送完成之前尝试发送项目 B?根据您的代码,听起来是的。
看起来您并不是真正的 want/need 队列,就其本身而言,您需要一个列表来跟踪正在发送的项目。 "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 是单线程的,因此您永远不会遇到代码的两部分同时尝试对队列进行操作的情况。当 ajax 调用异步完成时,您的回调代码将不会真正执行,直到当前正在执行的任何其他代码完成。
我看到的最大缺陷是您在 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];
};
尝试使用 ECMA 6 Promise 或来自 js framework.Promiseis 的任何更适合此任务的承诺。在 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);
}
});
};
将MyApp.queueIsLocked = false;
移动到服务器发送的回调