Given this code:
var something = function(callback) {
if(condition) {
Mongoose.findOne(id, function(err, doc) {
if(doc) {
callback(doc);
} else {
callback();
}
});
} else {
callback();
}
}
How would I rewrite it in a cleaner way so that 'callback' is just called in one place. I assume I can wrap this entire thing somehow and do that - I've seen it but cannot get it quite right.
Since you said there are complex steps to call the callback, try the below
var something = function(callback) {
var callCallback = function(doc){
//do all other things you want to do to call the callback
callback(doc);
};
if(condition) {
Mongoose.findOne(id, function(err, doc) {
if(doc) {
callCallback(doc);
} else {
callCallback();
}
});
} else {
callCallback();
}
}
var something = function (callback) {
var f = function (e, d) { callback(d) };
if (condition) {
Mongoose.findOne(id, f);
} else {
f();
}
}
my reasoning is that if d is false then we still can pass it on to callback and it will be the same almost as passing no arguments at all.
Related
I am trying to execute following array (avoid callbackHell) of functions(sync/async), in a sequential order, implementing function runCallbacksInSequence (I need to implement my own function to understand how callbacks work and avoid using Async.js).
Here is what I have so far. The function runCallbacksInSequence works well till it gets the same callback more than once. It stops and does not continue to execute the next callback. Ideally if it gets the same callback more than once it should not execute it second time and continue with the next callback.
If you have any ideas let me know what I am doing wrong and how I can fix it.
- no promises and async/await
function first(cb) {
setTimeout(function() {
console.log('first()');
cb(null, 'one');
}, 0);
}
function second(cb) {
setTimeout(function() {
console.log('second()');
cb(null, 'two');
}, 100);
}
function third(cb) {
setTimeout(function() {
console.log('third()');
cb(null, 'three');
}, 0);
}
function last(cb) {
console.log('last()');
cb(null, 'lastCall');
}
const cache = {};
function runCallbacksInSequence(fns, cb) {
fns.reduce(
function(r, f) {
return function(k) {
return r(function() {
if (cache[f]) {
return;
// f(function(e, x) {
// e ? cb(e) : k(x);
// });
} else {
cache[f] = f;
return f(function(e, x) {
return e ? cb(e) : k(x);
});
}
});
};
},
function(k) {
return k();
}
)(function(r) {
return cb(null, r);
});
}
const fns = [first, second, third, second, last];
runCallbacksInSequence(fns, function(err, results) {
if (err) return console.log('error: ' + err.message);
console.log(results);
});
Your function chaining depends on the call to k(). Therefore in your cache logic:
if (cache[f]) {
return;
} else {
// ...
The chain breaks.
What you want instead is this:
if (cache[f]) {
return k();
} else {
// ...
Alternative Implementation
One of the problems with the nested function implementation is that it is hard to reason about due to multiple nesting scopes (and multiple functions being juggled at once (r, f, k, cb).
A simpler approach to this is rather than trying to programmatically build callback hell you can use a queue instead (which is what async.js does). The idea is simple, pop() or shift() functions from an array until the array is empty:
function runCallbacksInSequence(fns, cb) {
let result = [];
let cache = {};
function loop () {
if (fns.length > 0) {
let f = fns.shift(); // remove one function from array
if (cache[f]) {
loop(); // skip this round
return;
}
cache[f] = f;
f(function(err, val) {
if (!err) {
result.push(val); // collect result
loop();
}
else {
// Handle errors however you want.
// Here I'm just terminating the sequence:
cb(err, result);
}
});
}
else {
cb(null, result); // we've collected all the results!!
}
}
loop(); // start the loop
}
As you can see, it's fairly easy to implement any flow logic with this structure. We can easily implement things like waterfall, parallelLimit etc. by controlling how we keep track of results and how many functions we remove from the array per iteration.
I guess with implementation based on cache you may omit doubled step with a direct k() invocation.
return;
if (cache[f]) {
return;
// f(function(e, x) {
// e ? cb(e) : k(x);
// });
Idea:
if (cache[f]) {
return k(function(e, x) {
return e ? cb(e) : k(x);
});
Your code is a little bit hard to read for me. So here is the alternative solution:
<script>
// The data
function first(cb) {
setTimeout(function () {
console.log('first()');
cb(null, 'one');
}, 0);
}
function second(cb) {
setTimeout(function () {
console.log('second()');
cb(null, 'two');
}, 100);
}
function third(cb) {
setTimeout(function () {
console.log('third()');
cb(null, 'three');
}, 0);
}
function last(cb) {
console.log('last()');
cb(null, 'lastCall');
}
const fns = [first, second, third, second, last];
// We need hash function to create the identifyer of the function
function hashCode(str) {
return Array
.from(str)
.reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0);
}
const cache = [];
function reducer(accumulator, currentFunction) {
// Take the functon string representation to detect "the same function"
const hash = hashCode(currentFunction.toString());
// Process the result of the current function and call the next one.
// We use "reduceRight" so `accumulator` is the next function in the chain.
const cb = function (fp, result) {
console.log(result);
// Cache the result;
cache[hash] = result;
accumulator();
}
// Run just a callback if we already have the result of the current function
return () => cache[hash] ? cb(null, cache[hash]) : currentFunction(cb);
}
fns.reduceRight(reducer, () => { })();
</script>
Result:
first()
one
second()
two
third()
three
two
last()
lastCall
If you do not want to process the cached result at all, then replace the call to the callback with the call to the accumulator directly.
return () => cache[hash] ? cb(null, cache[hash]) : currentFunction(cb);
replace with:
return () => cache[hash] ? accumulator() : currentFunction(cb);
Result:
first()
one
second()
two
third()
three
last()
lastCall
Solution without cache
It is much cleaner:
<script>
// Use the same data as in the example with cache
function reducer(accumulator, currentFunction) {
const cb = function (fp, result) {
console.log(result);
accumulator();
}
return () => currentFunction(cb)
}
fns.reduceRight(reducer, () => { })();
</script>
Result:
first()
one
second()
two
third()
three
second()
two
last()
lastCall
When performing the below function:
try {
Auction.deployed().then(function(contractInstance) {
contractInstance.startAuction(auctionname, duration, { from: buyerAddress }).then(function(result) {
console.log("AUCTION HAS STARTED!!");
console.log(result);
updateAuction(result.receipt);
});
});
} catch (err) {}
}
};
updateAuction = function(data) {
console.log("UPDATE AUCTIONS!");
....
The updateAuction function is not called (even though the console.log functions are working correctly and displaying a message). How can I call the updateAuction function?
This is because of the order in which you're defining the promise and the function.
When you use the syntax updateAuction = function () {}, whether or not you use a var, you must define it above the promise code, otherwise it won't be available. This is in the same way that if you were to write the following, it wouldn't work:
var b = a;
var a = 'Hello!';
This seems quite obvious that a won't be available before it's defined. The same thing applies to functions:
var b = function () {
a();
}
var a = function () {
console.log('Hello');
}
The b function won't have access to a, because it's not yet defined.
If, however, you use the definition of function updateAuction() {}, it will be hoisted, meaning it is defined before anything else.
There are many articles regarding how hoisting works, for example this one from scotch.io and this from Mozilla
var updateAuction = function(data) {
console.log("UPDATE AUCTIONS!");
...
}
try {
Auction.deployed().then(function(contractInstance) {
contractInstance.startAuction(auctionname, duration, { from: buyerAddress }).then(function(result) {
console.log("AUCTION HAS STARTED!!");
console.log(result);
updateAuction(result.receipt);
});
});
} catch (err) {}
You must define your function befor try. correct it like this
var updateAuction = function(data) {
console.log("UPDATE AUCTIONS!");
...
}
try {
Auction.deployed().then(function(contractInstance) {
contractInstance.startAuction(auctionname, duration, { from: buyerAddress }).then(function(result) {
console.log("AUCTION HAS STARTED!!");
console.log(result);
updateAuction(result.receipt);
});
});
} catch (err) {}
;
I have a JavaScript class that is meant to help deal with promises. First you add functions to an array, then it executes them pops them and calls itself to do the next one. At the end of the array it resolves that promise. My hope was to then propagate the resolution all the way up the stack of recursive calls. This will allow you to force multiple asynchronous functions to run sequentially using a simple set of commands. furthermore employ logic to modify the flow of the ansync functions.
function Sequencer() {
this.functionSequence = [];
this.addFunction = function (func) {
this.functionSequence.push(func);
}
this.getFunctionSequence = function () {
return functionSequence;
}
this.executeAll = function () {
var functionList = this.functionSequence;
var deferred = $q.defer();
if (functionList.length > 0) {
functionList[0]().then(function (result) {
if (result) {
functionList.splice(0, 1);
executeAll().then(function (resultInner) {
if (resultInner == true) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
} else {
functionList = [];
deferred.resolve(false);
}
});
} else {
deferred.resolve(true);
}
return deferred.promise;
}
}
I am getting ReferenceError: 'executeAll' is undefined
in this script, on the recursive call line "executeAll' just after the splice
the first function in the array is being executed(I was testing it with a modal pop up) and when it resolves it hits the splice, then it throws the error right on the executeAll line. Am I defining the function incorrectly? Am I calling it correctly as a recursive function?
use this.executeAll - assuming this will be correct, which it wont, so you'll need to account for that as well ... something like var self = this at the top of executeAll, then call self.executeAll
this.executeAll = function() {
var functionList = this.functionSequence;
var deferred = $q.defer();
var self = this; // save reference to this
if (functionList.length > 0) {
functionList[0]().then(function(result) {
if (result) {
functionList.splice(0, 1);
// need to use self here because "this" is not the "this" we want
self.executeAll().then(function(resultInner) {
if (resultInner == true) {
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
} else {
functionList = [];
deferred.resolve(false);
}
});
} else {
deferred.resolve(true);
}
return deferred.promise;
};
The reason this is not the this you "want" is due to how this works in javascript - there is plenty on info on stack exchange about using this - I'll find and link a good answer shortly
I offer this alternative code
this.executeAll = function() {
return this.functionSequence.reduce(function(promise, item) {
return promise.then(function(result) {
if (result) {
return item();
}
else {
throw "Fail"; // throw so we stop the chain
}
});
}, Promise.resolve(true))
.then(function(result) {
this.functionSequence = []; // clear out the added functions
return true; // fulfilled value is true as per original code
}.bind(this), function(err) {
this.functionSequence = []; // clear out the added functions
if (err == "Fail") {
return false; // convert the "Fail" to a fullfilled value of false as per original code
}
else {
throw err; // any other error - re-throw the error
}
}.bind(this))
};
I have the following example:
function foo1 (callback) {
if (!foo2(callback)) {
return;
}
console.log("Doing something");
/* do something */
}
function foo2 (callback) {
return callback ();
}
foo1 (function () {
console.log ("Hello World!");
});
I want to remove the if from foo1. Can I stop foo1 execution calling foo2? I am looking for something like this:
function foo1 (callback) {
foo2(callback); // calling foo2 this way I want to prevent
// the console.log below to be executed
console.log("Doing something");
/* do something */
}
Is there any way to do this?
Note that I don't want to throw an error. I just want to call the callback function and to stop the function execution.
Use case
Instead of this:
function a (options, callback) {
callback = callback || function () {};
if (typeof callback !== "function") {
callback = function (err) { console.log (err); }
}
if (!options.someField || options.someField.constructor !== Object) {
return callback ("someField should be an object");
}
/* do something */
}
I want to have:
function a (options, callback) {
validateFunction (callback, callback);
validateObject (options, callback);
validateObject (options.somField, callback);
/* do something */
}
If one of the validate* functions fails it should send the error via callback and stop a function execution.
If you can use promises:
function a (options) {
return validateObject(options).then(function(){
return validateObjecT(options.somField);
}).then(function(){
return validateObjecT2(options.somField);
}).then(function(){
return validateObjecT3(options.somField);
}).then(function(){
return validateObjecT4(options.somField);
}).then(function(){
/*do something*/
});
}
var validateObject = Promise.method(function(object) {
// Because this is inside Promise.method, thrown error
// will be equal to return Promise.reject(new Error("invalid"));
if (typeof object !== "object") throw new Error("invalid");
});
Alternatively function a can be also done like this:
function a (options) {
// If any validation fails, the do something is skipped
// and the code resumes at the next .catch() with the validation
// error passed as parameter
return Promise.all([
validateObject(options),
validateObject2(options),
validateObject3(options),
validateObject4(options),
validateObject(options)
]).then(function(){
/*do something*/
})
}
a({}).then(function() {
// Validation succeeded and everything was done
}).catch(function(e) {
// Validation or something else failed, e is the rejection error
});
Btw don't use strings as errors, a string is not an error.
That is, never do:
throw "foo";
callback("foo");
reject("foo") // When using promises
Instead do:
throw new Error("foo");
callback(new Error("foo"));
reject(new Error("foo")) // When using promises
There is a way, use throw
function foo2(callback) {
// do what you want to do
throw true;
}
And then catching it
try {
foo1();
}
catch (e) {
// if false, real error,
}
But it appears at a strange design. I hope that you have a valid reason and that it's clear to others whom is reviewing your code in the future.
I would use if statements:
function a (options, callback) {
if (!validateFunction (callback)) return;
if (!validateObject (options, callback)) return;
if (!validateObject (options.somField, callback)) return;
/* do something */
}
where the validateFunction function does not necessarily need 2 parameters if you call it always from such a scenario and the validate... functions always return a boolean with the result of the validation AND calling the callback in case of error.
function validateObject (options, errorCallback) {
if (!options.someField || options.someField.constructor !== Object) {
errorCallback("someField should be an object");
return false;
}
return true;
}
As many purists say, building an execution flow using try catch is not the right thing to do. There is also the performance issue. Using try catch is less performant then the control statements (e.g. if)
And there is though a try catch version that is faster and produces less code but I still don't prefer it because it is less clear in the code:
function a (options, callback) {
try {
validateFunction (callback);
validateObject (options);
validateObject (options.someField);
catch (err) {
callback(err);
return;
}
/* do something */
}
function validateObject (options, errorCallback) {
if (!options.someField || options.someField.constructor !== Object) {
throw "someField should be an object";
}
}
take a boolean for your function
function foo1(callback, exe) {
if (exe) {
foo2(callback);
} else {
console.log("Doing something");
/* do something */
}
}
I have code like the following:
function test(obj) {
if(//some conditon) {
obj.onload();
}else{
obj.onerror();
}
}
for(var i=0;i<4;i++){
test({
onload:function(e){
//some code to run
},
onerror:function(e){
break;
}
});
}
The gist is the test() function is a function to make an XHR request (it is actually an API of the Appcelerator Titanium platform so I have no control over it) and I'm looping something to call the test function. I need to break the loop on the onerror function, but I get an error saying the break is not inside a loop or switch statement. How can I rewrite this?
If your code sample does represent some actual code (i.e. all the processing is done in the same event loop tick), you may do the following:
function test(obj) {
if (some_condition) {
return obj.onload();
} else {
return obj.onerror();
}
}
var result;
for(var i=0; i<4; i++){
result = test({
onload:function(e){
//some code to run
return true;
},
onerror:function(e){
return false;
}
});
if (!result) {
break;
}
}
Otherwise, if there is something asynchronous done, you have to call test sequentially, and not in parallel. For example,
function test(obj) {
doSomeAjaxRequest(arguments, function (err, result) {
if (some_condition) {
obj.onload();
} else {
obj.onerror();
}
});
}
var f = function (i) {
if (i >= 4) return;
test({
onload:function(e){
//some code to run
f(i+1);
},
onerror:function(e){
break;
}
});
}
f(0);
Not possible. A for loop is a synchronous procedure while an Ajax request is not. That means when the onerror callback happens, the for loop is already done executing.
As an alternative you could introduce a check into your onsuccess handler which confirms no errors have happened yet.
var errorOccured = false;
for(var i=0;i<4;i++){
test({
onload:function(e){
if(errorOccured) return;
//some code to run
},
onerror:function(e){
errorOccured = true;
}
});
}
Bit hacky maybe but one could do something like this with a handler function.
var handler = function(array, callback){
for(var i=0, l=array.length, e=array[i]; i<l; ++i){
if(true === handler.exit){
handler.exit = false; //restore
return;
}
callback.call(null, e);
}
};
handler.exit = false;
handler([1,2,3], function(e){
console.log(e);
if(2 === e){
arguments.callee.caller.exit = true; // :-)
}
});
Didn't test this but maybe you could bind function to scope in call even?
var handler = function(a, f){
this.exit = false;
for(var i=0, l=a.length, e=a[i]; i<l; ++i){
if(true === this.exit){
this.exit = false; //restore
return;
}
callback.call(this, e);
}
};
handler([1,2,3], function(e){
console.log(e);
if(2 === e){
this.exit = true;
}
});
You cannot break the loop inside test() function. I think the best solution in this case would be to throw an exception inside test() function, then catch it in the loop and break.
Maybe you can make the 'onload' callback recursive?
Meaning, you just call the 'test' method inside the 'onload' handlerr, this would also mean you fire all requests in order instead of all at the same time.
I take the answer of penartur, but I modified without the "break":
function test(obj) {
doSomeAjaxRequest(arguments, function (err, result) {
if (some_condition) {
obj.onload();
} else {
obj.onerror();
}
});
}
var f = function (i) {
if (i >= 4) return;
test({
onload:function(e){
//some code to run
f(i+1);
},
onerror:function(e){
console.log('stop');
}
});
}
f(0);