node.js - nesting request, wait until request function is done - javascript

I use the following code
request(firstparams, function () {
var secondparams = {
// ******
};
request(secondparams, function () {
for (i=0; i<3; i++) {
var thirdparams = {
// ******
};
request(thirdparams, function () {
console.log('foo');
});
}
console.log('bar');
});
});
and want to get the result like:
foo
foo
foo
bar
but the result is:
bar
foo
foo
foo
Sorry for my poor English, if there is something ambiguity,I would try my best to explain. Thanks a lot ^ ^

The easies way to do what you want would be to use 'async' module which is the classical module to handle async problems.
To run 3rd calls in parallel and log 'bar' after each of them is finished you would do something like this:
const async = require('async');
let asyncFunctions = [];
for (let i = 0; i < 3; i+= 1) {
let thirdParams = {...};
asyncFunctions.push(function (callback) {
request(thirdparams, function (err, data) {
console.log('foo');
callback(err, data);
});
});
}
async.parallel(asyncFunctions, function (err, data) {
console.log('bar');
});
You are using callbacks. But there are also other ways to handle asynchrony in node.js like: promises, generators and async/await functions from ES7.
I think that may be useful for you do check out some articles like this one.

Related

Loop until all events are received in javascript

I have a function that waits for four external events (I have no control on them. They are randomly received)
function Foo() {
var this.data_1;
var this.data_2;
var this.data_3;
var this.data_4;
}
Foo.prototype.getData = function(){
deviceOne.on('data', (data) => {
this.data_1 = data;
});
deviceTwo.on('data', (data) => {
this.data_2 = data;
});
deviceThree.on('data', (data) => {
this.data_3 = data;
});
deviceFour.on('data', (data) => {
this.data_4 = data;
});
return {
"data_from_device_1": this.data_1,
"data_from_device_2": this.data_2,
"data_from_device_3": this.data_3,
"data_from_device_4": this.data_4
}
};
var foo = new Foo();
console.log(foo.getData()); // {'undefined', 'undefined', 'undefined', 'undefined'}
As you can see, the function won't wait for them and it will return 4 undefined objects. I've been looking for a solution and it seems like using async could help. It's just I didn't understand how to use it correctly in my case
To use async (and it does help here), first you need to do a promise version of your deviceXYZ.on:
const waitForData = device => new Promise((resolve, reject) => {
device.on('data', (data) => {
resolve(data);
});
// ...presumably hook up some kind of error event and use `reject` here...
});
Then (I've converted to class syntax here since if you're using async, you can use the new, simpler notation):
class Foo {
async getData() {
// ^^^^^
const data = await Promise.all([
// ^^^^^
waitForData(deviceOne),
waitForData(deviceTwo),
waitForData(deviceThree),
waitForData(deviceFour)
]);
this.data_1 = data[0];
this.data_2 = data[1];
this.data_3 = data[2];
this.data_4 = data[3];
return {
data_from_device_1: this.data_1,
data_from_device_2: this.data_2,
data_from_device_3: this.data_3,
data_from_device_4: this.data_4
};
}
}
Then code using it also has to be in an async function:
( async () => {
// ^^^^^
try {
const foo = new Foo();
console.log(await foo.getData()); // {'undefined', 'undefined', 'undefined', 'undefined'}
// ---------^
} catch (e) {
// Handle/report error
}
})();
...or it can use the promise directly in a non-async function:
const foo = new Foo();
foo.getData()
.then(result => {
console.log(result);
})
.catch(error => {
// Handle/report error
});
More:
async/await
Promise
Promise.all
class
Arrow functions
Side note: This is invalid syntax:
function Foo() {
var this.data_1; // Error here
var this.data_2;
var this.data_3;
var this.data_4;
}
At the moment, you don't declare properties in JavaScript. However, before too long (possibly ES2019, almost certainly ES2020) class syntax will be extended by the class fields proposal, currently at Stage 3 in the proposals process. When that happens, you can declare the "shape" of objects created by the Foo class (which can mean the objects go through fewer shape changes, which helps improve performance; it's also useful documentation for people reading the code). That would change Foo to look like this:
class Foo {
data_1; // These are field (property) declarations
data_2;
data_3;
data_4;
async getData() {
// ...
}
}

How to do a "for" loop with asynchronous condition in Javascript?

I have this function:
waitForFreeAccnt.prototype.isMemberFree = function () {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return true;
} else {
console.log('it is not free');
return false;
}
});
};
I would like to wait till the account is free for up to 10 seconds with something like that:
var test = function () {
for (var start = 1; start < 10; start++) {
var result = self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
But it doesn't work because self.api.getMemberInfo is asynch call. This is super frustrating with Javascript. Any other language it would be so simple to do. How do I force the for loop to wait for self.isMemberFree() to finish executing before proceeding with the loop?
Also to note, this is not in browser execution so I don't care about anything hanging.
When dealing with asynchronous code, you need to make use of callbacks. That is, if you want to do a() and b() in order but a() does something asynchronously, then you need to call b() from within a() once a() has a result. So not:
a(); // does something asynchronously
b(); // tries to use a()'s result but it isn't available yet
... but rather
a(b); // pass b to a() and a() will call it when ready
function a(callback) {
triggerAsyncFunction(function(result) {
if (result === something)
callback("a just finished");
});
}
Note that a() doesn't refer to b() by name, it just calls whatever function is passed in as an argument.
So applying that to your code, maybe something like this:
waitForFreeAccnt.prototype.isMemberFree = function (cbf) {
var self = this;
self.api.getMemberInfo(function () {
cbf(self.api.connect.accountType === 'FREE');
});
};
waitForFreeAccnt.prototype.testMemberXTimes = function(maxAttempts, callback) {
var attempts = 0;
var self = this;
(function attempt() {
self.isMemberFree(function(free) {
if (free)
callback(true);
else if (++attempts < maxAttempts)
setTimeout(attempt, 1000);
else
callback(false);
});
)();
};
this.testMemberXTimes(10, function(isFree) {
// the next part of your code here, or called from here
// because at this point we know we've tested up to
// ten times and isFree tells us the result
});
Note that the way I coded getMemberInfo() it is basically doing the same thing yours was, but instead of returning a boolean it is calling the callback function and passing the same boolean value that you were returning. (I've removed the console.log()s to make the code shorter.)
Note also that you could structure the above to use promises, but the end result will be the same.
You could return a Promise
waitForFreeAccnt.prototype.isMemberFree = function () {
return new Promise((reject, resolve)=>
// set a timeout if api call takes too long
var timeout = setTimeout(()=> reject(Error('API timeout')), 10000);
// make api call
this.api.getMemberInfo(()=> {
clearTimeout(timeout);
resolve(this.api.connect.accountType === 'FREE');
});
);
};
Then use it like this
whatever.isMemberFree().then(isFree=> {
if (isFree)
console.log('it is free');
else
console.log('it is not free');
})
// handle timeout or other errors
.catch(err=> {
console.log(err.message);
});
Building on naomik's answer, if you do it that way you can pretty easily use a for loop with it, using the (most likely) upcoming async/await feature - though it's not part of ES2015.
// Note "async" here! That will make "await" work. It makes the function
// return a promise, which you'll be able to either "await" or
// "test().then" later.
var test = async function () {
for (var start = 1; start < 10; start++) {
// Right here we're using "await" - it makes JavaScript *wait* for
// the promise that comes from self.isMemberFree() to be finished.
// It's really handy because you can use it in loops like "for" and
// "while" without changing the flow of your program!
var result = await self.isMemberFree();
console.log(result);
if (result) {
break;
} else {
self.api.pause(1000);
console.log('waiting');
}
}
};
For now you'll need to use a transpiler like Babel or Traceur before you can really use async/await, though. It's only supported in Microsoft Edge 14 right now.
And a big emphasis that what is returned from test() isn't whatever you directly return from inside it. If I do this:
var test = async function() { return 15; };
var result = test();
I'm not going to get 15 - I'll get a promise that will resolve as 15:
result.then(function(res) {
console.log(res); // 15
});
// or, use an async function again:
var main = async function() {
console.log(await res); // 15
};
main();
I don't have my work laptop today because it is Sunday, I'm coding this on sublime. Apologise if the syntax is a bit off.
To solve your problem I would recommend changing isMemberFree() to take in a callback function. This is because isMemberFree is async, and you will need a way to report the result after it has done the work.
Then change test function to use setTimeout API to wait a second.
Wrap the function call for isMemberFree() to be in a nested function and call it recursively, that way you'll have synchronize control over the async calls.
Look at the coding example:
waitForFreeAccnt.prototype.isMemberFree = function (done) {
var self = this;
self.api.getMemberInfo(function () {
var accType = self.api.connect.accountType;
console.log(accType);
if (accType === 'FREE') {
console.log('it is free');
return done(null, true);
} else {
console.log('it is not free');
return done(null, false);
}
});
};
var test = function () {
var testMembership = function(waitAttempt, isFree) {
if (isFree) {
return;
}
else if (waitAttempt > 10) {
// wait exceeded, do something.
return;
}
setTimeout(function() {
self.isMemberFree(function(err, isFree) {
testMembership(waitAttempt+=1, isFree);
});
}, /*total milliseconds in 1sec=*/1000);
}
testMembership(/*WaitAttempts=*/0, /*isFree=*/false);
};
What the above code does is that, presumably something has already been done to the member's account and now test function is called. So it waits for 1 second, then call isMemberFree function, this happens recursively until either isMemberFree() returns true OR the 10 seconds wait has been exceeded.

calling asyncwait from a callback

I have a nodejs library written using the async/await module. I try to consume it from a library which uses regular callbacks. Here is a sample code:
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var foo = async (function() {
var resultA = await (Promise.promisify(bar));
return 111;
})
function bar(callback) {
setTimeout(callback, 2000)
}
function moo() {
var f = async.cps(foo)
f(function(err, res) {
console.log(res)
})
}
moo()
I expected console.log to print 111 but instead it prints:
{ _bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_progressHandler0: undefined,
_promise0: undefined,
_receiver0: undefined,
_settledValue: undefined }
btw if I inline the foo implementation in the "async.cps" line it works (but this is not an option since its an external library).
Any idea?
I've looked at the library your using, and by the look of it (there isn't much, and certainly no working samples), you're not using it correctly.
async(fn) will return a function that accepts some input values and upon execution will return a promise (Probably standard Promises/A+). Inside, it will call fn with the input parameters, and will resolve the returned promise when fn has returned.
async.cps(...) will return a function that accepts some input values and a node style callback function (function (err, res)) and upon execution will return a void (undefined). Inside, it will call fn with the input parameters, and will call the callback function when fn has returned with the appropriate values.
what your code does is create a function with async(fn), then pass this function to async.cps(..) as if you called async.cps(async(fn)), but that doesn't make any sense.
What you could do if you wanted to "convert" a promise to a node style callback function (unpromisifying!) using this library is this:
function moo() {
var f = async.cps(function() {
return await (foo())
})
f(function(err, res) {
console.log(res)
})
}
You double-asynced the foo function. Here are your options, depending on whether you can modify the declaration of foo or not:
Leave foo async, and create a function f that accepts a node-style callback using cps:
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var foo = async(function() {
var resultA = await (Promise.promisify(bar));
return 111;
});
function bar(callback) {
setTimeout(callback, 1000);
}
function moo() {
var f = async.cps(function() {
return await(foo());
});
f(function(err, res) {
console.log(res);
});
}
moo();
Leave foo async, and use an async moo:
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var foo = async(function() {
var resultA = await (Promise.promisify(bar));
return 111;
});
function bar(callback) {
setTimeout(callback, 1000);
}
var moo = async(function() {
try {
var res = await(foo());
console.log(res);
} catch (err) {
}
});
moo();
Make foo already cps at the declaration:
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var foo = async.cps(function() {
var resultA = await (Promise.promisify(bar));
return 111;
});
function bar(callback) {
setTimeout(callback, 1000);
}
function moo() {
foo(function(err, res) {
console.log(res);
});
}
moo();

Javascript ES6 Generators

I'm diving into javascript generators and I'm really confused.
I'm using node#0.11.x to run this example:
function find() {
process.nextTick(function() {
it.next(1);
});
};
var it = (function* main() {
var k = yield find();
console.log(k);
})();
it.next();
Is there a way to grab the reference to next function inside the generator?
Something like:
function find(next) {
process.nextTick(function() {
next(1);
});
};
(function* main() {
var k = yield find(this.next);
console.log(k);
})().next();
To answer your question directly, you can't because this inside a generator function is not the generator instance, it is the context of the function call. You could do:
function find(next) {
process.nextTick(function() {
next(1);
});
};
var it = (function* main() {
var k = yield find(it.next.bind(it));
console.log(k);
})();
it.next();
but that's pretty hard to follow. Generally this would be accomplished with a coroutine library like co. With that, you would yield a promise, and when the promise is resolved, co will call .next with the value that the promise resolved with.
var co = require('co');
function find(){
return new Promise(function(resolve){
process.nextTick(function(){
resolve(1);
});
});
}
co(function * (){
var k = yield find();
console.log(k);
});

nodejs async same arguments to an array of functions?

How do I apply the same arguments to an array of functions?
Works:
async.parallel([func(arg, arg2), func2(arg, arg2)],
function() {
next();
});
The array has indeterminate / various functions functions inside though, so I'm not sure which functions I should be sending to the parallel method.
In various files I'm building the functions array:
funcArray.push(func2)
As a result I have an array like this:
[func, func2]
I would like to just do:
async.parallel(funcArray,
function() {
next();
});
and have all the same arguments be applied to all the functions. How can I do this? Thanks.
Ended up writing my own:
_.each(obj, function (func) {
func(req, res, function () {
i++
if (i == objSize) {
next()
}
})
})
Untested, but this ought to work:
var args = [ arg, arg2 ]
, funcCalls = []
;
for(var i = 0; i < funcArray.length; i++) {
funcCalls.push(function() {
funcArray[i].apply(this, args); }
);
}
async.parallel(funcCalls, next);
Or, if you already know how many arguments you'll have, you don't need to use apply in the middle section:
for(var i = 0; i < funcArray.length; i++) {
funcCalls.push(function() {
funcArray[i](arg, arg2); }
);
}
And finally you could really tighten it up with a map function as provided by e.g. Underscore.js:
async.parallel(_.map(funcArray,
function(func) { return function() { func(arg, arg2); } }
), next);
...but then the next guy who comes across your code might kill you.

Categories

Resources