here is my code
db.query(str, arr, function selectCb(error, results, fields) {
if (error) {
return proceed(false, {errno:'010',message:error.message}, request);
}
var q = async.queue(function (results, callback) {
// add the gib infor
if (results.refertype=='G') {
var input={};
input.fields="*";
input.gibname=results.refername;
gib.getgibinternal(input, makeCallback(i));
function makeCallback(index) {
return function(gresult) {
results.gib=gresult.data[0];
callback(results);
}
}
// add the user info
} else if(results.refertype=='U') {
var input={};
input.username=results.refername;
input.fields="*";
user.getuserinternal(input, makeCallbackuser(i));
function makeCallbackuser(index) {
return function(gresult) {
results.user=gresult.data[0];
callback(results);
}
}
}
}, results.length);
// assign a callback
q.drain = function() {
return proceed(true, self.results, self.request);
}
self.results=results;
for (var i=0; i<results.length; i++) {
// the first console
console.log(results[i]);
// add some items to the queue
q.push(results[i], function (results) {
results[i]=results;
self.results[i]=results;
//the second console.
console.log(results);
});
}
if (results.length==0) {
return proceed(true, results, request);
}
});
The out put of above code is :
// the first console
{ cardid: 30,
cardtype: 'I',
status: 'A',
refername: 'admin',
refertype: 'U' }
// the second console
{ '1': [Circular],
cardid: 30,
cardtype: 'I',
status: 'A',
refername: 'admin',
refertype: 'U',
user:
{ name: 'admin',
username: 'admin',
deleted: 'N' } }
how this '1': [Circular], get added .?
This bit:
q.push(results[i], function (results) {
is the same as this (with some renaming to make it easier to track the scope):
q.push(self.results[i], function(r) {
r[i] = r; // <------------------- Look at me!
self.results[i] = r;
//the second console.
console.log(r);
});
The self.results[i] change just comes from the self.results=results; right above your for loop. The interesting part is this:
r[i] = r;
If i is 1 you will have added a property named 1 to r whose value is r itself, hence the [Circular]. I would hazard a guess that results.length is 2 and that your function is acting as a closure over i and ending up with using the last value that i had and that's why you get the '1' rather than a '0' property.
I see three main Things that could be causing you trouble:
The classic closure problem with i.
Too many things were called results so it was easy to lose track of which one you were working with.
The circular reference: r[i] = r;.
Another possible source of confusion is that results and self.results are the same object but that might be okay.
Seems like it's a circular reference and console.log is muffling it. Try doing console.log(Object.keys(results['1'])); for more information on what's inside the Object 1
Related
I am having a hard time figuring out how promises work. I have the following code that I am executing iterate through an array.
runOpts.automationResults.reduce(function (p, val) {
return p.then(function () {
return pd.run(val);
});
}, Promise.resolve()).then(function (rallyResults) {
console.log('returned from method');
console.log('rallyResults ', rallyResults);
}, function (err) {
console.log('error in reduce', err);
});
It is calling the following method
ProcessDatabase.prototype.run = function (options) {
return new Promise((resolve, reject) => {
var dal = new db();
var resultsArray = {
results: [],
options: [],
testSet: [],
testIteration: '',
multipleSets: 0,
totalCount: 0
}
resultsArray.options = options;
dal.getAutomationResultsByBuild(options.Build).then((results) => {
resultsArray.results = results;
resultsArray.totalCount = results.data.length;
resolve(resultsArray);
})
}).then(function (resultsArray) {
var results = [];
//console.log('resultsArray', resultsArray);
//console.log(`Starting Rally publishing for Build '${resultsArray.options.Build}'...`)
if (resultsArray.options.Executiontype == 'ci') {
rallyApi.autoDiscoverTestSets().then((sets) => {
resultsArray.testSet = sets.sets;
resultsArray.testIteration = sets.iteration;
resultsArray.multipleSets = sets.sets.length > 1;
results.push(resultsArray);
console.log('results', results);
}, (err) => { reject(err); });
// totalResults = totalResults + results.data.length;
} else {
rallyApi.addTestSet('Automation: ' + resultsArray.options.type + ' - ' + resultsArray.options.build, config.get('rally.projects.testing.ref'), null, resultsArray.options.SessionUser).then((resp) => {
//console.log(resp);
console.log('New test set ' + resp.FormattedID + ' created.');
resultsArray.multipleSets = 0;
resultsArray.testSet = resp.FormattedID;
//console.log('resultsArray', resultsArray);
results.push(resultsArray);
console.log('results', results);
}, (err) => {
console.log(err);
});
}
console.log('results', results);
return Promise.all(results);
})
}
I have tried several different iterations of what I currently have and it always goes back to the calling .reduce before the results are complete in the ProcessDatabase.prototype.run method.
I have put console logs in and this is what I get. I can see that the final results array has all the information I need. I just haven't been able to figure out how to get it passed back to the calling .reduce.
---These are actually from the .reduce and are written to the log before the results in the method called
returned from method
rallyResults []
**----These are from the ProcessDatabase.prototype.run method called**
**This is the contents of the "results" set the first iteration through**
-- Found 2 test sets.
results [ { results: { _success: true, _message: '', _data: [Array] },
options:
{ Executiontype: 'ci',
Build: 'test_030518_103956',
Environment: 'dev',
SessionUser: 'https://rally1.rallydev.com/slm/webservice/v2.0/user/165547093296' },
testSet: [ [Object], [Object] ],
testIteration: 'https://rally1.rallydev.com/slm/webservice/v2.0/iteration/184203152680',
multipleSets: true,
totalCount: 4 } ]
New test set TS2969 created.
**This is the contents of the "result" set after the second iteration
through and what I trying to get passed back to the calling .reduce.**
results [ { results: { _success: true, _message: '', _data: [Array] },
options:
{ Executiontype: 'ci',
Build: 'test_030518_103956',
Environment: 'dev',
SessionUser: 'https://rally1.rallydev.com/slm/webservice/v2.0/user/165547093296' },
testSet: [ [Object], [Object] ],
testIteration: 'https://rally1.rallydev.com/slm/webservice/v2.0/iteration/184203152680',
multipleSets: true,
totalCount: 4 },
{ results: { _success: true, _message: '', _data: [Array] },
options:
{ Executiontype: 'regression',
Build: 'test_030518_110447',
Environment: 'dev',
SessionUser: 'https://rally1.rallydev.com/slm/webservice/v2.0/user/165547093296' },
testSet: 'TS2969',
testIteration: '',
multipleSets: 0,
totalCount: 6 } ]
Any help would be GREATLY appreciated.
Thanks
Christine
There are a number of issues in this code, but the main one is that when you run another async operation inside a .then() handler, you have to return that promise from the .then() to get it properly chained into the main promise chain. If you don't, then the parent promise is not linked to that internal promise at all (it becomes its own independent promise chain with no way for you to monitor it) and the parent promise does not wait for the internal promise.
Here are the changes made:
Remove promise anti-pattern of unnecessarily wrapping a promise in another promise (just return the promise you already have)
Return nested promises from within .then() handlers to chain them appropriately
Consolidate error logging in one place and rethrow error so it is returned properly
Remove Promise.all().
Remove results.push(resultsArray); since there does not appear to be any point to that. The way I've code it below, the top level promise resolves to resultsArray and since there is only one of them, I don't see a need to embed it in another array.
Here's the modified code:
ProcessDatabase.prototype.run = function (options) {
var dal = new db();
var resultsArray = {
results: [],
options: {},
testSet: [],
testIteration: '',
multipleSets: 0,
totalCount: 0
}
resultsArray.options = options;
return dal.getAutomationResultsByBuild(options.Build).then((results) => {
resultsArray.results = results;
resultsArray.totalCount = results.data.length;
return resultsArray;
}).then(function (resultsArray) {
//console.log('resultsArray', resultsArray);
//console.log(`Starting Rally publishing for Build '${resultsArray.options.Build}'...`)
if (resultsArray.options.Executiontype == 'ci') {
return rallyApi.autoDiscoverTestSets().then((sets) => {
resultsArray.testSet = sets.sets;
resultsArray.testIteration = sets.iteration;
resultsArray.multipleSets = sets.sets.length > 1;
return resultsArray;
});
} else {
return rallyApi.addTestSet('Automation: ' + resultsArray.options.type + ' - ' + resultsArray.options.build, config.get('rally.projects.testing.ref'), null, resultsArray.options.SessionUser).then((resp) => {
//console.log(resp);
console.log('New test set ' + resp.FormattedID + ' created.');
resultsArray.multipleSets = 0;
resultsArray.testSet = resp.FormattedID;
return resultsArray;
});
}
}).catch(err => {
// log error and rethrow
console.log(err);
throw err;
});
}
I have some JavaScript code:
var findLeastUsedPassage;
findLeastUsedPassage = function(StudentId) {
var passageCounts;
passageCounts = [];
return db.Passage.findAll({
where: {
active: true
}
}).each(function(dbPassage) {
var passage;
passage = dbPassage.get();
passage.count = 0;
return passageCounts.push(passage);
}).then(function() {
return db.Workbook.findAll({
where: {
SubjectId: 1,
gradedAt: {
$ne: null
},
StudentId: StudentId
},
include: [
{
model: db.WorkbookQuestion,
include: [db.Question]
}
],
limit: 10,
order: [['gradedAt', 'DESC']]
});
}).each(function(dbWorkbook) {
return Promise.resolve(dbWorkbook.WorkbookQuestions).each(function(dbWorkbookQuestion) {
var passageIndex;
passageIndex = _.findIndex(passageCounts, function(passageCount) {
return passageCount.id === dbWorkbookQuestion.Question.PassageId;
});
if (passageIndex !== -1) {
return passageCounts[passageIndex].count++;
}
});
}).then(function() {
passageCounts = _.sortBy(passageCounts, 'count');
return passageCounts;
});
};
and I want to unit test it (I think). I instrumented mocha to do the testing, but my test doesn't seem all that.. thorough:
describe('Finding the least used Passage', function() {
it('should have a function called findLeastUsedPassage', function() {
return expect(WorkbookLib.findLeastUsedPassage).to.exist;
});
return it('should return the least used passages for a student', function() {
return WorkbookLib.findLeastUsedPassage(10).then(function(passageCounts) {
var passageCountsLength;
passageCountsLength = passageCounts.length;
expect(passageCountsLength).to.equal(74);
expect(passageCounts[0].count).to.be.at.most(passageCounts[1].count);
expect(passageCounts[1].count).to.be.at.most(passageCounts[5].count);
expect(passageCounts[56].count).to.be.at.most(passageCounts[70].count);
return expect(passageCounts[70].count).to.be.at.most(passageCounts[73].count);
});
});
});
What's the right approach to unit testing something like this?
This is a great resource for understanding how to break up your code to able to test it.
Currently, you're code can't be tested well because the logic is all intermingled between multiple database calls, business logic, and glue code. What you need to do is break it all out into multiple named functions that each do one thing, like you do now. Expect that instead of creating the functions in the chain you should create them outside of the chain, then just call them in the promise chain.
var passageCounts = [];
function findAllActivePassages() {
passageCounts = [];
return db.Passage.findAll({
where: {
active: true
}
})
}
function countPassages(dbPassage) {
var passage;
passage = dbPassage.get();
passage.count = 0;
return passageCounts.push(passage);
}
function findAllSubjects(StudentId) {
return db.Workbook.findAll({
where: {
SubjectId: 1,
gradedAt: {
$ne: null
},
StudentId: StudentId
},
include: [
{
model: db.WorkbookQuestion,
include: [db.Question]
}
],
limit: 10,
order: [['gradedAt', 'DESC']]
});
})
// ...
findAllActivePassages()
.each(countPassages)
.then(function() {
return findAllSubjects(studentId)
})
// ...
Now you can test each function individually and in isolation to ensure that they do what you expect
So for starters, you probably want to break up your promise chains to make the discrete units of your code more apparent. I did some quick psuedo javascript (most familliar w/ node so apologies if this doesn't fit vanilla javascript as cleanly).
var p1 = db.Passage.findAll({ where: { active: true }})
var p2 = db.Workbook.findAll({
where: {
SubjectId: 1,
gradedAt: {
$ne: null
},
StudentId: StudentId
},
include: [
{
model: db.WorkbookQuestion,
include: [db.Question]
}
],
limit: 10,
order: [['gradedAt', 'DESC']]
});
Promise.all([p1, p2])
.then(function(results){
var passages = results[0]
var workbooks = results[1];
var passageCounts = {};
passages.foreach(function(passage){
passagecounts[passage.get().id] = 0
});
workbooks.foreach(function(workbook){
workbook.workBookQuestions.foreach(function(question){
return passageCounts[dbWorkbookQuestion.Question.PassageId] += 1;
})
});
return Promise.resolve(passageCounts)
}).then(function(passageCounts){
passageCounts = _.sortBy(passageCounts, 'count'); //this has to change but don't know what underscore offers for sorting an object used as a hashmap
return passageCounts;
});
Now as far as unit testing - you're looking to test discrete units of it so the following use cases seem reasonable:
Do I get any result back when expected?
If i give it specific values are they sorted in the way I expect?
If I have no results for either query does it break? Should it?
It may behoove you to break out the DB calls from the logic and pass the results into a method, makes testing some of the scenarios a bit easier.
I'm having a bit of trouble with an itterative function in nodejs.
I'm stepping through an object and checking if that object has any sub-objects attached (think: a star has a planet has a moon has an orbital station has a ship).
I'm trying to assemble this all into a nice array of objects to push to the client.
Here's the function:
var subNodeProc = function(nodeList,sqlP,itteration_count) {
var async = require('async');
--itteration_count;
async.each(nodeList,function(dd,cb){
var simple = {
sql:sqlP,
values:[dd.node_id],
timeout:40000
};
dd.subnodes = false;
connection.query(simple, function(err,rslt){
if (err) {
cb(err);
} else {
if (rslt.length > 0) {
var r = nodeList.indexOf(dd);
if (itteration_count > 0) {
rslt = subNodeProc(rslt,sqlP,itteration_count);
}
nodeList[r].subnodes = rslt;
}
cb();
}
});
},function(err){
if (err) {
return err;
} else {
return nodeList;
}
});
}
When I trigger the function it returns a nodelist of undefined. Can anyone give me a pointer in the right direction? I can't get it to work
Thanks!
Edit: here's a sample of the data I'm itterating over:
The SQL statement:
SELECT n.id as node_id, n.name, n.system_id, n.parent_id as parent_id FROM nodes as n WHERE n.parent_id = ?
Sample nodeList for input:
[ { node_id: 1,
name: 'Planet A',
system_id: 1,
parent_id: null,
},
{ node_id: 2,
name: 'Moon',
system_id: 1,
parent_id: 1,
},
{ node_id: 3,
name: 'Debris',
system_id: 1,
parent_id: 2,
},
{ node_id: 4,
name: 'Asteroid',
system_id: 1,
parent_id: 1,
} ]
Moon A has a parent_id of 1 and node_id of 2, moon A also has a ship (ship A, node_id:3, parent_id:2) orbiting it.
What I want :
[ { node_id: 1,
name: 'Planet A',
system_id: 1,
parent_id: null,
subnodes:[{
node_id: 2,
name: 'Moon A',
system_id: 1,
parent_id: 1,
subnodes: [{
node_id:3,
name: 'Ship A',
system_id:1,
parent_id:2
},
{...}]
},
{...}]
},
{...}]
It's hard to tell whether there are any other major issues because I cannot see the data which you are feeding the method. However, there is one major problem with this: you're attempting to return data from a method that uses asynchronous method calls.
The asynchronous way is to return values via a callback. In your code, the very last function in your example (the callback) is called from a completely different scope (from within the async framework) so your nodeList or err is being lost in a scope you don't control.
You need to rethink your code so that the returned data is passed to a callback. You could leverage the async callback for this. Ad a callback argument to your subNodeProc method. Then you can call that callback, after async has finished, passing it the nodeList:
var subNodeProc = function (nodeList, sqlP, itteration_count, cb) {
var async = require('async');
--itteration_count;
async.each(nodeList,function(dd, cb){
var simple = {
sql:sqlP,
values:[dd.node_id],
timeout:40000
};
dd.subnodes = false;
connection.query(simple, function(err, rslt){
if (err) {
cb(err);
} else {
if (rslt.length > 0) {
var r = nodeList.indexOf(dd);
if (itteration_count > 0) {
rslt = subNodeProc(rslt,sqlP,itteration_count);
}
nodeList[r].subnodes = rslt;
}
cb();
}
});
}, function (err) {
if (err)
throw err;
else
cb(nodeList);
});
}
You would then use the method like this:
subNodeProc(nodeList, sqlP, itteration_count, function (processed) {
console.log(processed);
/* do whatever you want afterwards here */
});
Okay, the solution was pretty obvious, once I sussed it out. Much thanks to #shennan for getting me going.
The key is:
as #shennan mentioned, you don't work with returns, as we're working asynchronously. This means callbacks
and
You have to trigger callbacks for each part of the function. This is not possible with just one function, so to get the objects returning you need two, each doing different parts of the original function.
Here's what I've come up with. Hope someone can look it over and give me an opinion...
// Main processing function.
var subNodeProc = function(nodeList,sqlP,itteration_count,cback) {
var async = require('async');
itteration_count--;
async.each(nodeList,function(dd,cb){
if (itteration_count > 0) {
// Trigger SQL Walker subNodeProcWalker with the necessary data (dd.node_id in this case, with a callback)
subNodeProcWalker(dd.node_id,sqlP,itteration_count,function(nl) {
// Hey look! the walker has done its business. Time to fill the subnode and tell async we're done with this array node.
dd.subnodes = nl;
cb();
});
}
},function(){
// At the end of the run, return the nodelist intact.
cback(nodeList);
});
}
// SQL Walker with callback for subNodeProc
var subNodeProcWalker = function(node_id,sqlP,itteration_count,cback){
// assemble the object for the query and do the query
var simple = {
sql:sqlP,
values:[node_id],
timeout:40000
};
connection.query(simple, function(err,rslt){
if (err) {
console.log('Error in Query');
console.log(simple);
console.log(err);
cback(false);
} else {
// no error and a result? Quick! Trigger subNodeProc again
if (rslt.length > 0) {
subNodeProc(rslt,sqlP,itteration_count,function(nodePol) {
// Lookie lookie! A result from subNodeProc! There's life there! Quick! tell this function we're done!
cback(nodePol);
});
} else {
cback(false);
}
}
});
}
After refactoring some of our scripts and putting some functions in separate custom modules in shared folder, I had encountered a very strange problem. Don't even know how to describe it properly. Think best way is to show it by an example. I apologise in advance, for long portions of code I will post here. I just don't know how else I can explain my problem.
We have several shared scripts with some of our common functional:
shared/commons-util.js - several convenient utility functions, among them is this one:
function getErrorHandler(errMessage, callback) {
return function(err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
}
exports.getErrorHandler = getErrorHandler
shared/commons-db.js - helper functions for working with database tables:
var waitingTime = +process.env.throttlingWaitTime || 0;
var retryCount = +process.env.throttlingRetryCount || 0;
function refreshThrottlingLimit() {
var throttlingLimit = +process.env.throttlingLimit || 0
if (LMD_GLOBALS.throttling.limit != throttlingLimit) {
LMD_GLOBALS.throttling.available += throttlingLimit - LMD_GLOBALS.throttling.limit
LMD_GLOBALS.throttling.limit = throttlingLimit
}
}
function throttledWrite(operation, table, data, callbacks, retriesLeft) {
refreshThrottlingLimit()
if (LMD_GLOBALS.throttling.limit && LMD_GLOBALS.throttling.available > 0) {
LMD_GLOBALS.throttling.available--
try {
console.log('Executing ' + operation + ' on table: ' + table.getTableName() + ', data: ', data, 'callbacks: ', callbacks)
table[operation](data, {
success: function(result) {
try {
console.log('throttledWrite: SUCCESS')
LMD_GLOBALS.throttling.available++
if (callbacks && callbacks.success) callbacks.success(result)
} catch (e) {
console.error('Exception in success callback', e)
}
},
error: function(err) {
try {
console.log('throttledWrite: ERROR')
LMD_GLOBALS.throttling.available++
err.table = table.getTableName()
err.operation = operation
err.data = data
if (callbacks && callbacks.error) callbacks.error(err)
} catch (e) {
console.error('Exception in error callback', e)
}
}
})
console.log(operation + ' started...')
} catch (e) {
console.error('Exception starting ' + operation, e)
}
} else if (retriesLeft > 0) {
setTimeout(throttledWrite, waitingTime, operation, table, data, callbacks, retriesLeft - 1)
} else {
if (callbacks && callbacks.error) callbacks.error(new Error('Aborting ' + operation + ' operation (waited for too long)'))
}
}
exports.throttledInsert = function(table, data, callbacks) {
throttledWrite('insert', table, data, callbacks, retryCount)
}
exports.throttledUpdate = function(table, data, callbacks) {
throttledWrite('update', table, data, callbacks, retryCount)
}
shared/commons.js - Just combining this two modules:
exports.db = require('../shared/commons-db.js')
exports.util = require('../shared/commons-util.js')
We are using this modules in scheduled script executeBackgroundJobs.js:
var commons = require('../shared/commons.js');
// Same high-order function as in `commons-util.js`. Only difference is that it is defined locally, not in the separate module.
function getErrorHandler(errMessage, callback) {
return function(err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
}
function executeBackgroundJobs() {
test();
}
function test() {
console.log('Testing paranormal activity in Azure');
var testTable = tables.getTable('test');
var callback = function(res) {
console.log('Test OK', res)
}
var errMessage = 'Could not write to `test` table';
var errHandler_1 = commons.util.getErrorHandler(errMessage, callback); // First handler we get from high-order function from custom module
var errHandler_2 = getErrorHandler(errMessage, callback); // Second handler must be the same, just getting from local function not from the external module
// This log lines just show that this two functions are identical
console.log('errHandler_1: ', errHandler_1);
console.log('errHandler_2: ', errHandler_2);
// We are calling `throttledUpdate` two times with two different error handlers for each invocation
commons.db.throttledUpdate(testTable, {
id: 'test-01', // Data object is intentionally illegal, we want update to fail
someColumn: 'some value #1'
}, {
success: callback,
error: errHandler_1
});
commons.db.throttledUpdate(testTable, {
id: 'test-02', // Data object is intentionally illegal, we want update to fail
someColumn: 'some value #2'
}, {
success: callback,
error: errHandler_2
});
}
Now here's the output of the one invocation of this job:
1: First call of throttledWrite:
INFORMATION:
Executing update on table: test, data: { id: 'test-01', someColumn: 'some value #1' } callbacks: { success: [Function], error: [Function] }
2: Start of the test(). It is already strange that logs are not in proper order, cause this functions so far are called synchronously (or at least I believe so). Does this mean that logging in Azure is happening in different threads? Anyway, this is not our main problem.
INFORMATION:
Testing paranormal activity in Azure
3: Here we just show the body of first handler
INFORMATION:
errHandler_1: function (err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
4: Here we just show the body of second handler
INFORMATION:
errHandler_2: function (err) {
console.log('inside error handler')
console.error(errMessage + '\nCause: ', err)
if (callback instanceof Function) {
callback(err)
}
}
5: table.update method was launched successfully in the first throttledWrite (no exceptions)
INFORMATION:
update started...
6: Second call of throttledWrite:
INFORMATION:
Executing update on table: test, data: { id: 'test-02', someColumn: 'some value #2' } callbacks: { success: [Function], error: [Function] }
7: table.update method was launched successfully in the second throttledWrite (no exceptions)
INFORMATION:
update started...
9: Error handler was called
INFORMATION:
inside error handler
10: From only the second throttledWrite
ERROR:
Could not write to `test` table
Cause: { _super: undefined,
message: 'Could not save item because it contains a column that is not in the table schema.',
code: 'BadInput',
table: 'test',
operation: 'update',
data: { id: 'test-02', someColumn: 'some value #2' } }
11: And that's all
INFORMATION:
Test OK { _super: undefined,
message: 'Could not save item because it contains a column that is not in the table schema.',
code: 'BadInput',
table: 'test',
operation: 'update',
data: { id: 'test-02', someColumn: 'some value #2' } }
First of all the strangest thing I cannot understand is why there was no callback invoked in first throttledWrite call.
And besides that, even in the second throttledWrite, where callback was called, no log was written from table.update-s error callback. Remember this lines from throttledWrite function:
error: function(err) {
try {
console.log('throttledWrite: ERROR')
LMD_GLOBALS.throttling.available++
err.table = table.getTableName()
err.operation = operation
err.data = data
if (callbacks && callbacks.error) callbacks.error(err)
} catch (e) {
console.error('Exception in error callback', e)
}
}
I have to add that this is not the actual code where I first encountered this problems. I tried to shorten code before posting. So you may don't fully understand the purposes of some methods. It does not matter, this code is just for demonstration.
That's it. I have three mysteries that I cannot explain:
Why callbacks are not called (in first case)?
Why did console.log('throttledWrite: ERROR') not write anything in log (when I am sure, it was called and was successful)?
Less critical, but still interesting, why are logs not in proper order?
Im trying to write a function that 1. adds an item to an observable array and 2. replaces the item if it already exists in the array
self.addNotification = function (name, availability, note) {
//see if we already have a line for this product
var matchingItem = self.notifications.indexOf(name);
if (matchingItem !== undefined) {
self.notifications.replace(self.notifications()[index(matchingItem)],
new Notification(self, name, availability, note));
}
else {
self.notifications.push(new Notification(self, name, availability, note));
}
};
What am I doing wrong?
Regards Anders
Here is my answer: fiddle
Hit F12 in Chrome or use FireBug in FireFox to see console log output.
var notifications = {
notifs: [],
updateNotifications: function(notification) {
'use strict';
var matchIndex;
for (matchIndex = 0; matchIndex < this.notifs.length; matchIndex += 1) {
if (this.notifs[matchIndex].name === notification.name) {
break;
}
}
if (matchIndex < this.notifs.length) {
this.notifs.splice(matchIndex, 1, notification);
} else {
this.notifs.push(notification);
}
}
};
notifications.updateNotifications({
name: 'John',
available: false,
note: "Huzzah!"
});
notifications.updateNotifications({
name: 'Jane',
available: true,
note: "Shazam!"
});
notifications.updateNotifications({
name: 'Jack',
available: true,
note: "Bonzai!"
});
notifications.updateNotifications({
name: 'Jane',
available: false,
note: "Redone!"
});
console.log(notifications);
Well, Array.prototype.indexOf never returns undefined. Its either -1 (not found) or any number starting with 0 for the array index.