Google Cloud Function async with multiple fetch requests - javascript

I'm new to both GCF and Javascript async and have been struggling with this. I perform a fetch call initially and then pass that response as a parameter to a second function which then also performs a separate fetch call.
During the second function, my empty initialized json gets properties added to it, and when that function completes, I want to notify the exports.helloHttp to then do res.end and terminate.
I've tried chaining an additional empty then() but it doesn't seem to be working.
My code:
var json = {}; // <- gets properties added to it during secondFunction()
exports.helloHttp = (req, res) => {
fetch("firstfetchurl.com",requestOptions)
.then(result => result.json())
.then(response => {
// next take the result and create a new product
return secondFunction(response);
})
.catch(error => console.log('error', error));
// res.end(JSON.stringify(json)); <- this is what I want my cloud function to output, but only after secondFunction completes
};

Here is the code that would do what you want (replace the fetch URLs and set the appropriate options)
const fetch = require('node-fetch');
exports.helloHttp = async (req, res) => {
return fetch("https://jsonplaceholder.typicode.com/users/1/albums") // First fetch
.then(firstFetchResponse => firstFetchResponse.json())
.then(firstFetchResponse => secondFunction(firstFetchResponse)) // Second fetch
.then(secondFunctionResponse => secondFunctionResponse.json())
.then(finalResponse => res.json(finalResponse)) // This line sends your response to the client
.catch(error => { console.error('Error', error); res.status(500).send('Server Error') }); // In case an error, log and send an error response
};
async function secondFunction(data) {
// Logic of your second function. Here just does another fetch using the data from the first request
let firstAlbumId = data[0].id
return fetch(`https://jsonplaceholder.typicode.com/albums/${firstAlbumId}/photos`);
}
The same function can use an await like this
exports.helloHttp = async (req, res) => {
try {
let response = await fetch("https://jsonplaceholder.typicode.com/users/1/albums") // Note the await on this line
.then(result => result.json())
.then(firstFetchResponse => secondFunction(firstFetchResponse))
.then(secondFetchResponse => secondFetchResponse.json());
res.json(response); // Finally you are sending the response here.
} catch (error) {
console.error(error);
res.status(500).send('Server Error');
}
};
Finally you would also need to make sure that the package.json has the dependency for node-fetch
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"node-fetch": "^2.6.0" // This line must be there
}
}
For sending the JSON response, it uses this method.

result.json() is not an asynchronous operation, therefore you don't need to use a then() block. The following should do the trick;
exports.helloHttp = (req, res) => {
fetch("firstfetchurl.com",requestOptions)
.then(result => {
return secondFunction(result.json());
})
.catch(error => console.log('error', error));
//...
Note that, depending on the exact goal of you helloHttp function, you may need to return the entire promises chain, as follows:
exports.helloHttp = (req, res) => {
return fetch("firstfetchurl.com",requestOptions) // Note the return
.then(result => {
return secondFunction(result.json());
})
.catch(error => console.log('error', error));
//...

Related

Wait for response from request before returning

I am trying to create a function with a GET request that returns a portion of the data from the GET request. However, it keeps returning before the data is retrieved, so I keep getting "undefined". How can I set this up so it actually waits for the data to be set before returning?
let getInfo = async () => {
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => { // Promise being here DOES work
request.on('response', (response) => {
response.on('data', (chunk) => {
//return new Promise((resolve, reject) => { //Promise being here does NOT work
let body = JSON.parse(chunk)
let info = body.data
if (info){
resolve(info);
}
reject();
//})
});
});
request.write('')
request.end()
}).then(data => {
console.log("From then: "+data)
return data
})
}
getInfo().then(data => {
console.log("From outside: "+data)
})
Edit: This is the updated version that still does not work. I am trying to use the native electron method and I don't see why this doesn't work. The "From then:" part displays the info correctly. But when run "From outside:" it prints undefined. Does the issue have anything to do with the response.on being nested inside the request.on?
Solution: As #NidhinDavid showed in his answer, the issue was that the promise was inside the 'response' listener. Moving the 'GET' request from start to finish inside the Promise fixed it to giving the correct output. I have updated my code to reflect that for future individuals.
let getInfo = () => {
let info;
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => {
request.on('response', (response) => {
response.on('data', (chunk) => {
request.write('')
request.end()
let body = JSON.parse(chunk)
info = body.data
if (info) {
resolve(info)
} else {
reject('Something went wrong');
}
});
});
})
}
getInfo()
.then(data => {
// this will be your info object
console.log(data)
})
.catch(err => {
// this will log 'Something went wrong' in case of any error
console.log(err)
})
You need to return inside your, on type event handler. Read more about asynchronous code and synchronous code here
I couldn't find the net module and the one which is included with Nodejs do not have request method. So to get the similar concept of event emiters and promise I am using http module and doing a http request to fetch json and parse it
'use strict'
var https = require('https');
const getInfo = async () => {
// create a new promise chain
// remember it is a chain, if one return is omitted
// then the chain is broken
return new Promise((resolve, reject) => {
var options = {
host: 'support.oneskyapp.com',
path: '/hc/en-us/article_attachments/202761727/example_2.json'
};
// start the request
https.request(options, function (response) {
var str = '';
// data arrives in chunks
// chunks needs to be stitched together before parsing
response.on('data', function (chunk) {
str += chunk;
});
// response body obtained
// resolve (aka return) the result
// or parse it, or do whatever you want with it
response.on('end', function () {
resolve(str)
});
// errors are another event
// listen for errors and reject when they are encountered
response.on('error', function (err) {
reject(err)
})
}).end()
})
}
//*********************************************
// using async await
//*********************************************
// if this is the entry point into app
// then top-level async approach required
(async ()=>{
try{
let data = await getInfo()
console.log("From ASYNC AWAIT ")
console.log(JSON.stringify(JSON.parse(data)))
}
catch (err) {
console.log("operation failed, error: ", err)
}
})();
//************************************************
// using promise chains
//************************************************
getInfo()
.then((data)=>{
console.log("FROM PROMISE CHAIN ")
console.log(JSON.stringify(JSON.parse(data)))
})
.catch((err)=>{
console.log("operation failed, error: ", err)
})
Tyr this, it might works for you,
let info;
const getInfo = async (_url)=>{
const response = await fetch(_url);
const data = await response.json();
info = data;
} ;
const url = "some url";
getInfo(url);
console.log(info);
Async function always returns a promise, so either consume that promise or internally await the data and assign it to some variable.
Check for the valid data required in info by logging it to the console.

how to make second request if .catch on promise is executed?

how can I make a second request if .catch on promise is executed , in order to get the data successfully
because if .catch is executed I did not received the data. and I have to refresh the page again to get the data.
fetch("https://randomuser.me/api")
.then(result => result.json())
.then(data => {
console.log(data)
})
.catch(error => console.log(error))
You just want to retry in the catch? You will need to call the function that makes the request again. This is called recursion. Ie:
function makeRequest() {
fetch("https://randomuser.me/api")
.then((result) => result.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
return makeRequest(); // Calls itself recursively
});
}
However this introduces the possibility of an infinite loop, so you need some way to break out, maybe like this:
function makeRequest(attempt=0) {
const maxRetries = 3;
if (attempt > maxRetries) {
throw new Error(`Could not fetch user after ${attempt} attempts`);
}
fetch("https://randomuser.me/api")
.then((result) => result.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
// Call self and increment the attempt number.
// Very important to ensure that the break condition
// can be met or we can end up calling the function
// forever with no way to escape. This is called an
// infinite loop.
return makeRequest(attempt + 1);
});
}
You can also make the logic more complex for retries, like introduce a timeout before the next request if attempt is gt 0, add exponential backoff etc.
Always remember to be careful of infinite loops when using recursive functions.
You can put the fetch and .json() calls into a function, then call that function once (immediately), and call it again inside the .catch if needed:
const getData = () => fetch("https://randomuser.me/api")
.then(result => result.json())
getData()
.then(console.log)
.catch((error) => {
console.log(error);
return getData();
});
.catch((error2) => {
// Could not get response after 2 attempts
console.log(error2);
});

Promise rejection: Cannot read property 'status' of undefined

I am very new to nodejs and I am getting the above-mentioned error for the below code. any help is appreciated. thankyou.
PushNotifications.sendMessageToAll(announcement, (err,res) => {
if (err){
return res.status(500).send()
}
res.status(200).send()
}
sendMessageToAll: function (notification, cb) {
payload = {....}
admin.messaging().subscribeToTopic(tokens,topic).then((res) =>{
return admin.messaging().sendToTopic('/topics/NATA', payload)
}).then((res) =>{
console.log('sent', res)
cb(undefined,res)
}).catch((err) =>{
console.log('Subscribed error',err)
cb(err,undefined)
})
}
}
The error you are getting is because the property status doesn’t exist on undefined that is getting passed back on this line:
cb(err,undefined)
The caller expects a response object, because it wants to use the response object to set a HTTP status code and send back a response to the browser:
return res.status(500).send()
The solution is simple: pass res (short for 'response') to the error callback, instead of undefined:
}).catch((err) =>{
console.log('Subscribed error', err)
cb(err, res)
})
You're literally passing undefined to your callback cb which expects the second parameter to be a response object
.catch((err) =>{
console.log('Subscribed error',err)
cb(err,undefined)
})
You need to make your callback be able to handle having no res to set the status.
In the catch block of your promise you call the callback without the res input:
admin.messaging().subscribeToTopic(tokens,topic).then((res) =>{
return admin.messaging().sendToTopic('/topics/NATA', payload)
}).then((res) =>{
console.log('sent', res)
cb(undefined,res)
}).catch((err) =>{
console.log('Subscribed error',err)
cb(err,undefined) // <-- HERE the second param is res!
})
}
}
EDIT
res is not defined in the catch block. A way could be:
let outerRes;
admin.messaging().subscribeToTopic(tokens,topic).then((res) =>{
outerRes = admin.messaging().sendToTopic('/topics/NATA', payload)
return outerRes;
}).then((res) =>{
console.log('sent', res)
cb(undefined,res)
}).catch((err) =>{
console.log('Subscribed error',err)
cb(err,outerRes) // <-- HERE the second param is res!
})
}
}

Return data from Promise and store it in variable after API Call

I´m pretty new to Promises and found many examples here how to access the actual value which is always done with console.log. But my goal is to store the result in a variable and work with it.
getdata = () =>
fetch(
"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo"
)
.then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("This is an error");
}
})
.then(data => {
console.log(data);
});
getdata();
This code works. Can you help me to rewrite it that the getdata() function allows me to store the result in a variable. Return does not work since I will receive another pending Promise.
You can do it like this:
getdata = () =>
fetch(
"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo"
).then(response => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("This is an error");
}
});
getdata().then(data => {
//I can do whatever with data
});
Of course you would also want to handle the scenario where the request failed, so you could also chain a .catch(). Alternately, if you have your build process configured for it, you can use async and await so you could do:
try {
const data = await getdata();
} catch(err) {
}
This would need to be in a function marked as async
Well at first we need to declare a variable let's say temp. Then use fetch API to request our query with URL. If server status is 200 then it will return a promise, we need to use then method by passing any argument (res, response, r anything...) and then a fat arrow function (=>) so that we can make the response as json format. After then we need to use another then method to return the json output and assign the value to our declared temp variable.
But if there is any error like 500, 400, 404 server error we need to use catch method with err argument and console it out.
let temp;
fetch('https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=MSFT&outputsize=full&apikey=demo')
.then(res => res.json())
.then(data => temp = data)
.catch(err => console.log(err));

How to define a function which returns Promise to a Express Route function?

I have a business leve database module called "db_location" which uses the node-fetch module to get some data from a remote server via REST API.
**db_location.js** DB LOGIC
const p_conf = require('../parse_config');
const db_location = {
getLocations: function() {
fetch(`${p_conf.SERVER_URL}/parse` + '/classes/GCUR_LOCATION', { method: 'GET', headers: {
'X-Parse-Application-Id': 'APPLICATION_ID',
'X-Parse-REST-API-Key': 'restAPIKey'
}})
.then( res1 => {
//console.log("res1.json(): " + res1.json());
return res1;
})
.catch((error) => {
console.log(error);
return Promise.reject(new Error(error));
})
}
};
module.exports = db_location
I would need to call this function within a Route function so as to separate database processing from controller.
**locations.js** ROUTE
var path = require('path');
var express = require('express');
var fetch = require('node-fetch');
var router = express.Router();
const db_location = require('../db/db_location');
/* GET route root page. */
router.get('/', function(req, res, next) {
db_location.getLocations()
.then(res1 => res1.json())
.then(json => res.send(json["results"]))
.catch((err) => {
console.log(err);
return next(err);
})
});
When I ran http://localhost:3000/locations, I received the following error.
Cannot read property 'then' of undefined
TypeError: Cannot read property 'then' of undefined
It seems the Promise was empty or something wrong down the Promise chain going from one response object to another? What is a best practise for solving this kind of scenario?
EDIT 1
If I changed the getLocations to return res1.json() (which I think is a non-empty Promise according to the node-fetch documentation):
fetch(`${p_conf.SERVER_URL}/parse` + '/classes/GCUR_LOCATION', { method: 'GET', headers: {
'X-Parse-Application-Id': 'APPLICATION_ID',
'X-Parse-REST-API-Key': 'restAPIKey'
}})
.then( res1 => {
return res1.json(); // Not empty as it can be logged to `Promise Object`
})
.catch((error) => {
console.log(error);
return Promise.reject(new Error(error));
})
And the route code was changed to :
db_location.getLocations()
.then(json => res.send(json["results"]))
.catch((err) => {
console.log(err);
return next(err);
})
The exactly same error was thrown.
You need getLocations to return a Promise. At the moment, it's running a fetch, but that fetch isn't connected to anything else, and getLocations is returning undefined (and of course you can't call .then on uundefined)
Instead, change to:
const db_location = {
getLocations: function() {
return fetch( ...
Also, since you're not doing anything special in the getLocations catch block, you might consider omitting it entirely and let the caller handle it.
Your function doesn't return anything.
If you want to use a promise, you need return it.

Categories

Resources