I have a number of stock tickers in a JSON file that I want to use to send multiple GET requests to get the price of that stock. The problem I'm having is how to send them off in parallel and how to resolve them.
Here is the code I have tried:
const stocks = require('./stocks.json')
var request = require("request");
var stockTickers = stocks.map(x => x.stockopediaTicker)
stockTickers.forEach(ticker => {
var promises = []
var options = {
method: 'GET',
url: 'https://www.stockopedia.com/ajax/get_prices/' + ticker + '/'
};
let todaysQuote = new Promise(function (res, rej) {
request(options, function (error, response, body) {
rej(error)
res(body)
});
})
promises.push(todaysQuote)
});
Promise.all(promises)
.then((results) => {
console.log(results)
}).catch(err => console.log(err))
You were on the right path with your code.
You just need to only call rej(err) if there is actually an error
You shouldn't be attempting to share the same options object with all your requests (that might or might not cause problems)
You need to declare promises in a higher scope where it can be used with Promise.all(promises):
Here's what it would look like after fixing up those issues:
const stocks = require('./stocks.json')
const request = require("request");
let promises = [];
let stockTickers = stocks.map(x => x.stockopediaTicker);
stockTickers.forEach(ticker => {
let todaysQuote = new Promise(function (res, rej) {
let options = {
method: 'GET',
url: 'https://www.stockopedia.com/ajax/get_prices/' + ticker + '/'
};
request(options, function (error, response, body) {
if (error) {
rej(error);
} else {
res(body);
}
});
})
promises.push(todaysQuote)
});
Promise.all(promises)
.then((results) => {
console.log(results);
}).catch(err => console.log(err))
The request-promise module is simpler to use as it already includes a promise wrapper and it's probably easier to use .map() to accumulate your array of promises.
const stocks = require('./stocks.json')
const rp = require("request-promise");
Promise.all(stocks.map(x => {
let options = {
method: 'GET',
url: 'https://www.stockopedia.com/ajax/get_prices/' + x.stockopediaTicker + '/'
};
return rp(options);
})).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
You should check if there is an error and only rej if there is an error:
let todaysQuote = new Promise(function (res, rej) {
request(options, function (error, response, body) {
if(error) {
return rej(error)
}
res(body)
});
})
Right now, you're "rejecting" every response.
I'm not sure about "request" but using "request-promise-native" you can simplify some things like this.
const stocks = require('./stocks.json');
const request = require('request-promise-native');
const parseStocks = (stocks)=>Promise.all(stocks.map(requestQuote));
const requestQuote = ({stockopediaTicker})=>{
const options = {
method: 'GET',
url: `https://www.stockopedia.com/ajax/get_prices/${stockopediaTicker}/`
};
return request(options)
}
parseStocks(stocks).then(console.log).catch(console.log)
Normally I refrain from suggesting dependency changes in an answer, but in this case, 'request-promise-native' is suggested by the 'request' docs. If you plan on using promises you may want to switch. It's usually best practice to avoid combining callbacks and promise chains together.
Related
I'm new to Node JS and I can't seem to figure out why i'm getting an 'undefined' when using an async function. I've wrapped the async function in a promise and it resolves after an API request has been completed.
let req = (url) => new Promise((resolve, reject) => {
var options = {
'method': 'GET',
'url': url,
'headers': {
'Content-Type': 'application/json'
}
}
request(options, function (error, response) {
if (error) {
reject(error)
} else {
resolve(response.body)
}
})
})
.then((results) => {
//console.log(results) //this shows the results when uncommented!
console.log('request has been completed')
})
.catch((error) => {
console.log(error)
})
Here is the function that triggers the 'req' function and then says 'undefined' when I log the value:
let getAllData = async (url) => {
var endpoint = `${url}?apikey=${settings.api_key}`
let res = await req(endpoint)
console.log('run after request') //successfully runs after request is resolved above
console.log(res) //gives me 'undefined'
}
Here is my output.
request has been completed
run after request
undefined
What am I doing wrong here?
You are using a .then / .catch as part of the function definition - which does not make sense. Those constructs should be used as part of the function execution.
Define your function like this:
let req = (url) => new Promise((resolve, reject) => {
var options = {
'method': 'GET',
'url': url,
'headers': {
'Content-Type': 'application/json'
}
}
request(options, function (error, response) {
if (error) {
reject(error)
} else {
resolve(response.body)
}
})
})
Then, at execution time, you do this:
let getAllData = async (url) => {
var endpoint = `${url}?apikey=${settings.api_key}`
let res = await req(endpoint)
console.log('run after request')
console.log(res) // should give response.body
}
OR you do this:
let getAllData = async (url) => {
return new Promise((resolve, reject) => {
var endpoint = `${url}?apikey=${settings.api_key}`
req(endpoint)
.then(res => {
console.log('run after request')
console.log(res) // should give response.body
resolve(res)
})
.catch(err => {
console.error(err)
reject(err)
})
})
}
Basically, define the function and then... either call it with async/await syntax, or with promise-style .then/.catch syntax.
Return a value in your then or remove the then and catch block and do this instead when you call the promise with await:
let res = await req(endpoint).catch(err => console.log(err));
I started by creating a return statement in the request function (I have linked a picture) and then console.log it outside of the function but that didn't work out.
My server code
var options = {
'method': 'POST',
'url': 'http://localhost:8080/ES_Part1/api/user/getUser',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'username': username,
'password': password
}
};
requestToApi(options, function(error, response) {
if (error) throw new Error(error);
console.log("Send form data to remote api and to return the user from Spring")
console.log(response.body);
return response.body
});
var fromapi = response.body;
res.end();
Example:
I suggest you use a Promise-based approach here rather than the callback-style that you're using for requestToApi. If you're using the request package, there is a Promise-based version available.
Alternative solution would be to create a promise yourself, like such:
var requestToApiAsPromise = (options) => {
return new Promise((resolve, reject) => {
requestToApi(options, (error, response) => {
if (error) {
reject(error)
return
}
resolve(response.body)
})
})
}
Then you can use this method in your middleware:
app.post("/checkUser", (req, res) => {
async function process() {
try {
var username = req.body.username
var password = req.body.password
var options = {...}
var response = await requestToApiAsPromise(options)
// response => response.body
// do whatever
res.end()
} catch (error) {
next(error)
}
}
process()
})
This method uses async/await so that it lets you write your code as if you were doing things synchronously, so it's making it easier to make asynchronous calls and have them "wait" before the next line gets executed.
👨🏫 If you want to get respose.body outside the handler, than you can use this code below 👇:
// an example get function
app.get('/users', async(req, res) => {
var options = {
'method': 'POST',
'url': 'http://localhost:8080/ES_Part1/api/user/getUser',
'headers': {
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
'username': username,
'password': password
}
};
const result = new Promise((resolve, reject) => {
requestToApi(options, function(error, response) {
if (error) return reject(error);
return resolve(JSON.parse(response.body));
});
})
// make sure, to use async in your function
// because we're using await here
var fromapi = await result;
// It's working here
console.log(fromapi);
res.end();
})
That code above 👆, only an example that you can use to read response.body. If you want to handle the error from that code above, you can use like this code below:
try {
// make sure, to use async in your function
// because we're using await here
var fromapi = await result;
// It's working here
console.log(fromapi);
} catch(ex) {
// print error response
console.log(ex.message);
}
I hope it's can help you 🙏.
I need to transform this code into clean code that uses callbacks, because this code does not allow me to use body information elsewhere.
const endpoints = [];
function getDevicesFromPartnerCloud() {
var options = {
method: 'GET',
url: 'https://database-dcda.restdb.io/rest/endpoints',
headers: {
'cache-control': 'no-cache',
'x-apikey': '*****************************'
}
};
request(options, function (error, response, body) {
var data = JSON.parse(body);
data.forEach(function(data, index) {
let endpoint = createSceneEndpoint(data._id, data.name);
endpoints.push(endpoint);
});
});
return endpoints;
}
I think the cleanest way to do it would be to use a Promise to handle the asynchronous request. One of the most important things to remember is that functions should ideally do only one thing. That way, they are easier to test, reason about, and refactor. I would pull the code that actually makes the request into a separate function and have it return the body, then have your getDevicesFromPartnerCloud call that new function, get the data back, and process it however it wants. Most importantly, this "frees" the data from being stuck in the request callback, because you're wrapping it in a promise, and resolving it when the data is available.
Something like:
const endpoints = [];
function requestDevices() {
return new Promise(function(resolve, reject) {
const options = {
method: 'GET',
url: 'https://database-dcda.restdb.io/rest/endpoints',
headers: {
'cache-control': 'no-cache',
'x-apikey': '*****************************',
},
};
request(options, function(error, response, body) {
if (error) {
reject(error)
}
resolve({ response: response, body: body });
});
});
}
async function getDevicesFromPartnerCloud() {
const devicesResponse = await requestDevices();
const data = JSON.parse(devicesResponse.body);
data.forEach(function(data, index) {
const endpoint = createSceneEndpoint(data._id, data.name);
endpoints.push(endpoint);
});
// Do whatever else you need with devicesResponse.body
return endpoints;
}
If you wanted to go more of an es6 direction, maybe something like
let endpoints;
const requestDevices = () =>
new Promise((resolve, reject) => {
request(
{
method: 'GET',
url: 'https://database-dcda.restdb.io/rest/endpoints',
headers: {
'cache-control': 'no-cache',
'x-apikey': '*****************************',
},
},
(error, response, body) => (error ? reject(error) : resolve(body)),
);
});
const getDevicesFromPartnerCloud = async () => {
try {
const body = await requestDevices();
const data = JSON.parse(body);
endpoints = data.map(({ _id, name }) =>
createSceneEndpoint(_id, name),
);
// Do whatever else you need with devicesResponse.body
// doStuff(body)
return endpoints;
} catch (e) {
console.error(e);
}
};
I'm trying to make REST apis with the serverless framework.
Some of the functions are asynchronous.
So I'm using Promise.
But the promise is not working (no response)
So, I'm using the await keyword. It works fine.
I think this is bad way. How to use promise in serverless framework?
Any advice or suggestion would be appreciated. Thank you in advance.
You can use the promise of many ways. Personally, separate the promise in another function.
I made a example with request module:
const request = require("request");
// The promise
const requestPromise = (url, options) =>
new Promise((resolve, reject) => {
options = options || {};
const processRequest = (err, response) => (err ? reject(err) : resolve(response));
request(url, options, processRequest);
});
// You can use like this
module.exports = (event,context) => {
let url = event.url;
requestPromise(url)
.then(response => {
// Do something
context.succeed({succeed: true /* put return data here */})
})
.catch(details => context.fail({error: true, details: details}));
}
// Or this
module.exports = async (event,context) => {
try {
let url = event.url;
let response = await requestPromise(url);
// Do something
context.succeed({succeed: true /* put return data here */});
} catch (details) {
context.fail({error: true, details: details});
}
}
If you use async/wait, you need add try/catch to handler errors.
I am coding a serverless-kubeless api now for the mysql world database. I had to solve this problem yesterday. I arrived at the following solution. It's not feature complete. But you didn't ask for that. So here is a working GET endpoint which accepts various query parameters to customise the query.
'use strict';
const pool = require('./database');
module.exports.handler = async (event, context) => new Promise((resolve, reject) => {
let request = event.extensions.request;
let response = event.extensions.response;
try{
let handleResults = (err, results, fields) => {
if(err){
response.status(500).send({
success: false,
message: err.message,
});
}else{
response.status(200).send({
success: true,
count: results.length,
data: results,
});
}
}
if(typeof(request.query.id) !== "undefined"){
// search for a specific region by id
if (Number.isNaN(Number(request.query.id))) {
response.status(500).send({
success: false,
message: "id query param was not a number",
});
}
pool.query("select id,name,code,country_id from regions where id = ?", [request.query.id], handleResults);
}else if(typeof(request.query.country) !== "undefined"){
// search for a region list from a specific country
if (Number.isNaN(Number(request.query.country))) {
response.status(500).send({
success: false,
message: "country query param was not a number",
});
}
pool.query("select id,name,code,country_id from regions where country_id = ?", [request.query.country], handleResults);
}else{
response.status(400).send({
success: false,
message: "Could not find country, or region query parameter. Require a search term"
});
}
}catch(exception){
response.status(500).send({
success: false,
message: exception.message
});
}
});
and database.js:
const mysql = require("mysql");
const util = require('util');
const pool = mysql.createPool({
connectionLimit: 10,
host: process.env.DATABASE_HOSTNAME,
user: process.env.DATABASE_USERNAME,
port: process.env.DATABASE_PORT,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
});
pool.getConnection((err, connection) => {
if (err) {
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
console.error('Database connection was closed.');
}
if (err.code === 'ER_CON_COUNT_ERROR') {
console.error('Database has too many connections.');
}
if (err.code === 'ECONNREFUSED') {
console.error('Database connection was refused.');
}
}
if (connection) connection.release();
return;
});
// Magic happens here.
pool.query = util.promisify(pool.query);
module.exports = pool;
I commonly do stuff with Promises in my serverless projects:
//this would me in a module like: services/myhttpservice.js (for example)
//wrap the GET HTTP request in a Promise
module.exports.GetUrlPromise = function(url, cookie_session_value) {
console.log(new Date().getTime() + " GetUrlPromise() CALLED: " + url);
var j = request.jar();
if(cookie_session_value){
var cookie1 = request.cookie(cookie_name + '=' + cookie_session_value);
j.setCookie(cookie1, cookie_domain);// domain used by the cookie, maybe make more generic?
}
// create the "Basic" auth header
var auth = "Basic " + Buffer.from(basic_username + ":" + basic_password).toString("base64");
//create request options
var options = {
'method': 'GET',
'url': url,
'jar': j,
'headers': {
'Authorization': auth,// set Basic auth header that is the base64 of the un:pw combo
'Content-Type': 'application/json'
}
};
return new Promise((resolve, reject) => {
request(options, function (error, response, body) {
if(error){
console.log('error:', error);
reject(error);
}else{
console.log('statusCode:', response && response.statusCode);
// object for returning response results
var http_resp = {};
http_resp._session = GetCookieValue(response);
http_resp.body = body;
http_resp.statusCode = response.statusCode;
//http_resp.response = response;
http_resp.requestType = 'GET';
console.log(JSON.stringify(http_resp));
resolve(http_resp);
}
});
});
}
It gives me the ability to make promised calls to my services easily:
//in my controller code:
myhttpservice.GetUrlPromise(page_url, user_session)
.then((http_resp)=>{ etc...
Await and async are not bad practices if used correctly.
If you don't have promises depending on each other you can call them in 'parallel' by adding all promises (without await) in an array and use const responses = await Promise.all(promisesArray) to wait for all responses to be successful.
For more information refer to this answer which explains very well Call async/await functions in parallel
I'm trying to grab text from an API that only returns a string of text ((here)) and having troubles throwing that out in a response. When posting, it comes out as [object Response], and the console.log doesn't show the text I want out of it.
The code I'm using:
fetch('http://taskinoz.com/gdq/api').then(
function(response) {
console.log(response);
throttledSendMessage(channel, response);
return response;
})
.catch(function(error) {
throttledSendMessage(channel, "An error has occured");
})
Log can be found here
Thanks for looking with me, couldn't find a solution :/
I think that because fetch returns a Response you need to call one of the functions on Response in order to get at the body's text. Here's an example:
fetch('https://github.com/')
.then(res => res.text())
.then(body => console.log(body));
Probably the problem is in async behavior of node.js. You can read more here
Also, I'm assume you use this package to make fetch request in node.js.
And assume that throttledSendMessage function is synchronous.
About your problem, just try to rewrite co de to use async/await for cleaner solution.
// We'll use IIFE function
(async () => {
const fetchResult = await fetch('http://taskinoz.com/gdq/api')
// Here fetch return the promise, so we need to await it again and parse according to our needs. So, the result code would be this
const data = await fetchResult.text();
throttledSendMessage(channel, data);
return data;
})()
Fetch is not available in nodejs , you could use the node-fetch https://www.npmjs.com/package/node-fetch , or you could use this fetch function:
const https = require('https');
const http = require('http');
function fetch(url, options = {}) {
return new Promise((resolve, reject) => {
if (!url) return reject(new Error('Url is required'));
const { body, method = 'GET', ...restOptions } = options;
const client = url.startsWith('https') ? https : http;
const request = client.request(url, { method, ...restOptions }, (res) => {
let chunks = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
chunks += chunk;
});
res.on('end', () => {
resolve({ statusCode: res.statusCode, body: chunks });
});
});
request.on('error', (err) => {
reject(err);
});
if (body) {
request.setHeader('Content-Length', body.length);
request.write(body);
}
request.end();
});
}
module.exports = fetch;
you could use it like this inside your code :
const result = await fetch(
`https://YOUR_URL`,
{
method: 'PUT',
body: JSON.stringify({ test: 'This is just a test', prio: 1 }),
},
);