Node Js loop in Promise All not waiting to get request from SOAP - node.js

Hi I try to use for for loop data and send request to soap api. The problem is my program seems like it didn't wait to get request from soap request
here is my example data
data = [
{
store: "store1",
product: "product2",
},
{
store: "store2",
product: "product3",
}
]
exports.thisIsFunc = async (req, res, next) => {
Promise.all(data).then(result => {
for (let item of result) {
i++
console.log(i)
args.CustomerCode = item.store
args.ItemNo = item.product
const getApi = apiSend()
}
});
}
export function apiSend() {
return new Promise ((resolve, reject) => {
soap.createClient(url, (err, client) => {
client.getDataFromAPI(args, (err, result) => {
console.log(result)
return result
})
});
});
}
as you see I try to use new Promise in sendApi function but sometimes It stop the error show up
TypeError: Cannot read property 'getDataFromAPI' of undefined
sometimes it return response from api. The reason that I didn't use async,await in soap because I try to change soap function into async function but it didn't work.

apiSend() has the following issues:
You ignore both callback errors.
You never resolve() or reject() the promise you return.
You can fix it like this:
export function apiSend() {
return new Promise ((resolve, reject) => {
soap.createClient(url, (err, client) => {
if (err) return reject(err);
client.getDataFromAPI(args, (err, result) => {
if (err) return reject(err);
console.log(result)
resolve(result);
})
});
});
}
Then, in thisIsFunc() you have a number of issues:
There's no point in using Promise.all() on a static array of values. It only does something useful if you pass it an array with at least one promise in it.
There's no declaration of i or args
You don't do anything with the promise returns from apiSend()
It's not really clear what you're trying to do here, but if you want thisIsFunc() to return a promise that resolves with an array of results from all the calls to apiSend(), that could be structured like this:
exports.thisIsFunc = () => {
return Promise.all(data.map(item => {
let args = {
CustomerCode: item.store,
ItemNo: item.product
};
return apiSend(args);
}));
}
This implementation uses data.map() to iterate the array and create an array of promise from calling apiSend(). It then uses Promise.all() to collect all the results from the array of promises into an array of results that is the resolved value of the single returned promise.
It appears you were attempting to declare thisIsFunc() as an Express request handler. You can do that, but then you will need to complete the request inside that function by sending a response for both success and error conditions. As I've shown it above, this is a helper function that retrieves a set of data and then returns a promise that resolves to an array of results. You can use that in a request handler as needed or you can add more code to this to make it into a request handler that sends a response to the incoming request and returns errors responses appropriately.

Related

MongoDB with node.js - how to return data async

I've tried a few approaches to this - what I have is all my modules in one file that interact with mongodb, and in another, the express route functions that call into those async functions looking for data. The problem is that the data is available in the async function, but is not returned to the calling function, and I'm not sure how to pass it back properly (not sure if it's an issue of not waiting for the async function to return and returning the array early, or if I'm actually returning it wrong).
Here is the code for the calling function
router.route('/').get((req, res) => {
db.getItemsFromCollection("Plants").then( (itemArr) => {
console.log(itemArr);
})
});
And the db function (two attempts, one commented out)
getItemsFromCollection: async function(collectionName) {
let itemArr = [];
const collection = client.db().collection(collectionName);
/*collection.find().forEach(function( doc) {
itemArr.push(doc);
//console.log(doc);
})
return itemArr;*/
collection.find({}).toArray(function(err, item) {
if (err) throw err;
console.log(item);
itemArr.push(item);
})
return itemArr;
},
If you are using mongodb then it already supports Promise syntax:
However, all async API calls support an optional callback as the final
argument, if a callback is provided a Promise will not be returned.
Then you can make getItemsFromCollection become:
getItemsFromCollection: async function (collectionName) {
// let itemArr = [];
const collection = client.db().collection(collectionName);
const items = await collection.find({}).toArray(); // don't pass callback param
console.log(items);
// I guess you want to get back an array of the collection item
return items;
},

Async - Await issue using Twitter API

I'm currently trying to practice using an API with Twitter API.
I'm using Twit package to connect to twitters API but when I try to do a get request I get
Promise { pending }
I have tried using Async-Await but I'm not sure what I'm doing wrong here.
Here is my code:
const Twit = require('twit');
const twitterAPI = require('../secrets');
//Twit uses OAuth to stablish connection with twitter
let T = new Twit({
consumer_key: twitterAPI.apiKey,
consumer_secret: twitterAPI.apiSecretKey,
access_token: twitterAPI.accessToken,
access_token_secret: twitterAPI.accessTokenSecret
})
const getUsersTweets = async (userName) => {
let params = { screen_name: userName, count: 1 }
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data
})
return userTweets
}
console.log(getUsersTweets('Rainbow6Game'));
Problem
The biggest assumption that is wrong with the sample code is that T.get is expected to eventually resolve with some data.
const userTweets = await T.get('search/tweets', params, await function (err, data, response) {
if (err) {
return 'There was an Error', err.stack
}
return data // 'data' returned from here is not necessarily going to be received in 'userTweets' variable
})
callback function provided as last argument in T.get function call doesn't have to be preceded by an 'await'.
'data' returned from callback function is not necessarily going to be received in 'userTweets' variable. It totally depends on how T.get is implemented and can not be controlled.
Reason
Thing to be noted here is that async / await works well with Promise returning functions which eventually get resolved with expected data, however, that is not guaranteed here
Relying on the result of asynchronous T.get function call will probably not work because it returns a Promise { pending } object immediately and will get resolved with no data. The best case scenario is that everything with your function will work but getUsersTweets function will return 'undefined'.
Solution
The best solution is to make sure that your getUsersTweets function returns a promise which eventually gets resolved with correct data. Following changes are suggested:
const getUsersTweets = (userName) => {
return new Promise ((resolve, reject) => {
let params = { screen_name: userName, count: 1 }
T.get('search/tweets', params, function (err, data, response) {
if (err) {
reject(err);
}
resolve(data);
})
}
}
The above function is now guaranteed to return expected data and can be used in the following way:
const printTweets = async () => {
const tweets = await getUsersTweet(userName);
console.log(tweets);
}
printTweets();
From what I can see on your code, getUserTweets is an async function, so it will eventually return a promise. I'm assuming you will use this value on another function, so you will need to use it inside an async function and use await, otherwise you will always get a promise.
const logTweets = async (username) => {
try {
const userTweets = await getUsersTweets(username);
// Do something with the tweets
console.log(userTweets);
catch (err) {
// catch any error
console.log(err);
}
}
If logging is all you want and you wrapped it inside a function in which you console.log it, you can call that function directly:
logTweets('someUsername');

Using async/await to return data in hapi.js's handler function

I want to return dataSet from my handler function. However it's nested inside my promise chain. I'm attempting to use await/async but the value of data is still undefined. Thoughts on how to do this?
handler: (request, h) => {
let data: any;
connection.connect((err) => {
if (err) {
console.error("Error-------> " + err);
}
console.log("Connected as id " + connection.threadId);
connector.getAllEvents()
.then(async dataSet => {
console.log(dataSet);
data = await dataSet;
});
});
return data;
}
Err is not being thrown since logging to the console prints out the values I'm looking for.
In order to do this you would need to make handler return a Promise, and within the handler, wrap the connection.connect block with a Promise.
e.g.
handler: (request, h) => {
// wrap connector.connect(...) in a Promise
return Promise<any>((resolve, reject) => {
connection.connect(err => {
if (err) {
console.error("Error -----> ", err);
// error in connection, propagate error via reject
// and do not continue processing
return reject(err);
}
console.log("Connected as id " + connection.threadId);
connector.getAllEvents()
// don't think you need this to be async
// as connector.getAllEvents() will should return a Promise<T>
// and .then() is like a .map() so its first argument is a T
// rather than a Promise<T>
.then(dataSet => {
console.log(dataSet);
// we finally have our value
// so we propagate it via resolve()
resolve(dataSet);
});
});
});
}
Data is not initialized when you return it. You can test it by adding another log statement just before return, you'll see it prints before console.log(dataSet);
I don't know what connection.connect returns (what framework is it?), but you can promisify it. Then you either return a promise to "connect and get the data" and let the caller wait on it, or you await on it inside your function and return the data after promise is fulfilled.

Function with async request in Node js

I have a loop, which iterates over array and in every iteration I have to do a http request, like this:
var httpsRequest = require('request')
var getData = function(id) {
var result;
httpsRequest({
url: 'https://link/'+id,
}, (error, resp, body) => {
if(resp.statusCode == 200) {
result = JSON.parse(body);
}
});
//here I would like to wait for a result
}
var data = [];
for(row in rows) {
data.push(getData(row.ID))
}
resp.send(JSON.stringify(data)) //I send data back to the client
I cannot do the rest of the for loop in callback, I have to wait for a result which will be returned from a function getData and move to the next iteration.
How to handle this?
PS I know I could use callback function but what if after the last iteration program will send the response (last line above) before the last getData execution finish?
Regards
As stated in the answer by Johannes, the use of promises is a good idea. Since you're using request I'd like to propose an alternative method by using request-promise which is a promisified version of 'request' using bluebird.
The requests will in this case return a promise, and by using .map() you can create an array of promises that you can await using Promise.all(). When all promises are resolved, the response can be sent! This also differs from the use of .reduce(), which only will start to execute the next request as soon as the previous one is done. By using an array of promises, you can start all the requests at the same time.
var httpsRequest = require('request-promise')
var getData = function(id) {
return httpsRequest({
url: 'https://link/' + id,
}, (error, resp, body) => {
if(resp.statusCode == 200) {
return JSON.parse(body);
} else {
//Throw error, this will be caught in the .catch()
throw error;
}
});
}
var promises = rows.map(function(row){
return getData(row.ID)
});
Promise.all(promises)
.then(function(results){
//All requests are done!
//The variable results will be an array of all the results in the same order as they were requested
resp.send(JSON.stringify(results));
})
.catch(function(error){
//Handle the error thrown in the 'getData' function
});
If you need to wait for each iteration to be done before starting another one, you can use Promises and reduce. If you only want to wait for all requests to be finished it's better to use map + Promise.all as explained in Daniel Bs answer.
// i asume rows is an array as you wrote you iterate over one.
const results = [];
rows.reduce((previous, row) => {
return previous.then(() => getData(row.ID).then(result => results.push(result)) // do whatever you want with the result
);
}, Promise.resolve())
.then(() => resp.send(JSON.stringify(results)));
const getData = (id) => {
return new Promise((resolve, reject)=> {
httpsRequest({
url: 'https://link/'+id,
}, (error, resp, body) => {
if(error) return reject(error);
if(resp.statusCode == 200) {
return resolve(JSON.parse(body));
}
return resolve(); // if you want to pass non 200 through. You may want to do sth different here
});
});
};

Use promises for multiple node requests

With the request library, is there a way to use promises to simplify this callback?
var context = {};
request.get({
url: someURL,
}, function(err, response, body) {
context.one = JSON.parse(body);
request.get({
url: anotherURL,
}, function(err, response, body) {
context.two = JSON.parse(body);
// render page
res.render('pages/myPage');
});
});
Here's a solution using the Bluebird promises library. This serializes the two requests and accumulates the results in the context object and rolls up error handling all to one place:
var Promise = require("bluebird");
var request = Promise.promisifyAll(require("request"), {multiArgs: true});
var context = {};
request.getAsync(someURL).spread(function(response, body) {
context.one = JSON.parse(body);
return request.getAsync(anotherURL);
}).spread(response, body)
context.two = JSON.parse(body);
// render page
res.render('pages/myPage');
}).catch(function(err) {
// error here
});
And, if you have multiple URLs, you can use some of Bluebirds other features like Promise.map() to iterate an array of URLs:
var Promise = require("bluebird");
var request = Promise.promisifyAll(require("request"), {multiArgs: true});
var urlList = ["url1", "url2", "url3"];
Promise.map(urlList, function(url) {
return request.getAsync(url).spread(function(response,body) {
return [JSON.parse(body),url];
});
}).then(function(results) {
// results is an array of all the parsed bodies in order
}).catch(function(err) {
// handle error here
});
Or, you could create a helper function to do this for you:
// pass an array of URLs
function getBodies(array) {
return Promise.map(urlList, function(url) {
return request.getAsync(url).spread(function(response.body) {
return JSON.parse(body);
});
});
});
// sample usage of helper function
getBodies(["url1", "url2", "url3"]).then(function(results) {
// process results array here
}).catch(function(err) {
// process error here
});
Here is how I would implement chained Promises.
var request = require("request");
var someURL = 'http://ip.jsontest.com/';
var anotherURL = 'http://ip.jsontest.com/';
function combinePromises(context){
return Promise.all(
[someURL, anotherURL].map((url, i)=> {
return new Promise(function(resolve, reject){
try{
request.get({
url: url,
}, function(err, response, body) {
if(err){
reject(err);
}else{
context[i+1] = JSON.parse(body);
resolve(1); //you can send back anything you want here
}
});
}catch(error){
reject(error);
}
});
})
);
}
var context = {"1": "", "2": ""};
combinePromises(context)
.then(function(response){
console.log(context);
//render page
res.render('pages/myPage');
}, function(error){
//do something with error here
});
Doing this with native Promises. It's good to understand the guts.
This here is known as the "Promise Constructor Antipattern" as pointed out by #Bergi in the comments. Don't do this. Check out the better method below.
var contextA = new Promise(function(resolve, reject) {
request('http://someurl.com', function(err, response, body) {
if(err) reject(err);
else {
resolve(body.toJSON());
}
});
});
var contextB = new Promise(function(resolve, reject) {
request('http://contextB.com', function(err, response, contextB) {
if(err) reject(err);
else {
contextA.then(function(contextA) {
res.render('page', contextA, contextB);
});
}
});
});
The nifty trick here, and I think by using raw promises you come to appreciate this, is that contextA resolves once and then we have access to it's resolved result. This is, we never make the above request to someurl.com, but still have access to contextA's JSON.
So I can conceivable create a contextC and reuse the JSON without having to make another request. Promises always only resolve once. You would have to take that anonymous executor function out and put it in a new Promise to refresh that data.
Bonus note:
This executes contextA and contextB in parallel, but will do the final computation that needs both contexts when both A & B are resolved.
Here's my new stab at this.
The main problem with the above solution is none of the promises are reusable and they are not chained which is a key feature of Promises.
However, I still recommend promisifying your request library yourself and abstaining from adding another dependency to your project. Another benefit of promisifying yourself is you can write your own rejection logic. This is important if you're working with a particular API that sends error messages in the body. Let's take a look:
//Function that returns a new Promise. Beats out constructor anti-pattern.
const asyncReq = function(options) {
return new Promise(function (resolve, reject) {
request(options, function(err, response, body) {
//Rejected promises can be dealt with in a `catch` block.
if(err) {
return reject(err);
}
//custom error handling logic for your application.
else if (hasError(body)) {
return reject(toError(body));
}
// typically I just `resolve` `res` since it contains `body`.
return resolve(res);
}
});
};
asyncReq(urlA)
.then(function(resA) {
//Promise.all is the preferred method for managing nested context.
return Promise.all([resA, asyncReq(urlB)]);
})
.then(function(resAB) {
return render('page', resAB[0], resAB[1]);
})
.catch(function(e) {
console.err(e);
});
You can use the request-promise library to do this. In your case, you could have something like this, where you chain your requests.
request
.get({ url: someURL })
.then(body => {
context.one = JSON.parse(body);
// Resolves the promise
return request.get({ url: anotherURL });
})
.then(body => {
context.two = JSON.parse(body);
res.render('pages/myPage');
})
.catch(e => {
//Catch errors
console.log('Error:', e);
});
By far the easiest is to use request-promise library. You can also use use a promise library like bluebird and use its promisify functions to convert the request callback API to a promise API, though you may need to write your own promisify function as request does not use the standard callback semantics. Lastly, you can just make your own promise wrapper, using either native promises or bluebird.
If you're starting fresh, just use request-promise. If you're refactoring existing code, I would just write a simple wrapper for request using bluebird's spread function.

Resources