Node.js / Koa: wait for API call before response - javascript

I have only basic JS knowledge (mainly jQuery), so this might be a really simple one.
And do realise performance-wise it might not be the best solution, but for now this is just proof of concept.
I have this simple Koa app. All I'm trying to do is to call an external API (Airtable) when the /callapi route is accessed and add some data from the API response to ctx.body.
However, I get a 404 Not Found when going to http://localhost:3000/callapi
I understand this is probably because the API call is asynchronous, so Node.js / Koa don't wait for it to finish and continue the execution, but there is no code setting the response body elsewhere, so it results in 404.
How can I achieve that?
Happy to use other routing middleware or additional middleware if needed.
I believe the Promises with async/await is the new way to go, which Koa embraces, so if I can somehow add / wrap this, it would be the best I guess.
const KoaRoute = require('koa-route');
const Koa = require('koa');
const app = new Koa();
var Airtable = require('airtable');
var base = new Airtable({apiKey: 'keyI6rZxwsXXXXXXX'}).base('appXXXXXX');
app.use(KoaRoute.get('/callapi', async function (ctx) {
await base('Couples').find('reclnxjiMeSljrzP0', function(err, record) {
console.log('Retrieved', record.id);
ctx.body = "Record ID from API: " + record.id;
});
}));
app.listen(3000);
console.log('listening on port 3000');

Looks like await is wrong. Please try this.
...
app.use(KoaRoute.get('/callapi', async function (ctx) {
/* please check return value if it returns correct value. */
try {
const record = await getBase();
console.log('Retrieved', record.id);
ctx.body = "Record ID from API: " + record.id;
} catch (err) {
// handle exception
}
}));
...
EDITED: Please add the following function and try again above thing.
function getBase() {
return new Promise((resolve, reject) => {
base('Couples').find('reclnxjiMeSljrzP0', function(err, record) {
console.log('Retrieved', record.id);
if (err) {
reject(err)
} else {
resolve(record);
}
});
});
}

Related

How to wait for a variable to be populated by an api request before passing it to a webpage as an argument?

I'm new to JavaScript and cannot seem to make this work , the topic of quiz depends on the user input... when the user presses next , I get the topic (this also takes user to the main quiz page), then i have to fetch data from the api with the topic as a parameter... I have to process the result of the fetch operation.. Then I have to pass that info to to the main quiz page... but the variable that is supposed to be populated by the fetch request is still undefined when i pass is to the main quiz page
var Allquestions;
var sheetdb = require('sheetdb-node');
// create a config file
var config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
var client = sheetdb(config);
function downloadquestions(topic) {
console.log(topic);
client.read({ limit: 2, sheet: topic }).then(function(data) {
console.log(data + " in client.read func")
processQuestions(data);
}, function(err){
console.log(err);
});
}
async function processQuestions(data) {
console.log(data + "data in process");
Allquestions = JSON.parse(data);
console.log(Allquestions[0].Question + " This is defined");
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
// app.post("/" , urlencodedParser ,(req , res) => {
// console.log(req.body.topic);
// })
app.get("/questions", urlencodedParser , (req , res) => {
downloadquestions(req.body.topic);
console.log(Allquestions + " this is undefined");
res.render("/pages/quizpage" , {Allquestions})
})
There are a few issues with your code, you have a broken promise chain, client.read( is a promise, and that promise is going nowhere. You either return it, or await it. To be able to await your will need to also mark your route (req, res) as async too.
Your code is a little mixed up, you have Allquestions as a global var, this isn't great for multi-user, as the last topic is going to override this each time.
Also try and avoid swallowing exceptions in utility functions, try and keep your exception handling at the top level, eg. in your case inside your req/res handler.
So with all this in mind, your refactored code could look something like ->
const sheetdb = require('sheetdb-node');
// create a config file
const config = {
address: 'https://sheetdb.io/api/v1/9djmf8ydc7hwy',
};
//sheetdb
// Create new client
const client = sheetdb(config);
async function downloadquestions(topic) {
const data = await client.read({ limit: 2, sheet: topic });
return processQuestions(data);
}
function processQuestions(data) {
return JSON.parse(data);
}
app.get("/", (req, res) => {
res.render("pages/index", { title: "Home"});
});
app.get("/questions", urlencodedParser , async (req , res) => {
try {
const allQuestions = await downloadquestions(req.body.topic);
res.render("/pages/quizpage" , {Allquestions});
} catch (e) {
console.error(e);
res.end('There was an error');
}
})

NodeJS: How to wait for a for-loop of async requests to complete before proceeding with code?

I am relatively new to node and trying to create an web app that allows users to get retrieve information from the youtube api by providing a list of youtube channel IDs(already in server) by going to my /retrieve route.
Currently I am looping through an array of objects with channel IDs, and for each ID I use a setTimeOut function to send 1request/500ms (due limitations of the youtube API) and using the request.get request module. I am trying to continue code and res.send AFTER all the requests to youtube is completed. Furtherfore, for each data obj I received from each youtube request, I am parsing and pushing them to the youtubeinfo array.
I have a nodeJS version of v12.16.3.
Here's my code below, sorry for the messiness:
var youtubeinfo = []
router.get('/retrieve', async function(req, res, next) {
const length = datajs.length
console.log(length);
var promiseArray = [];
for(i = 0; i < length; i++){
promiseArray.push(new Promise((resolve, reject) => {
(function(i){
setTimeout(function(){
const channelID = listOfID[i]['Channel ID']
const url = youtubeAPIChannelID.concat(apiKey)
request.get(url, (err, response, data)=>{
if (!err) {
parsedData = JSON.parse(data)
if (parsedData.items){
const parsedYoutubeobj = {
"custom_collection": {
"title": parsedData.items[0].brandingSettings.channel.title,
"body_html": parsedData.items[0].brandingSettings.channel.description,
"image": {
"src": parsedData.items[0].brandingSettings.image.bannerImageUrl,
"alt": "Rails Logo"
}
}
}
youtubeinfo.push(parsedYoutubeobj)
console.log("----------------------")
console.log(parsedYoutubeobj);
youtubeinfo.push(parsedYoutubeobj);
resolve()
} else {
console.log("something is wrong...check if youtube channel still exist")
reject(error)
}
} else{
console.log("statusCode: "+ response.statusCode)
console.log("err: "+ err);
reject(error)
}
})
}, 500 * i);
}(i));
}))
}
res.send(await Promise.all(promiseArray));
});
How can I correctly apply promises/async/await to have my code continue after all the async requests are made?
I figured that using promises here would a solution. How do I edit res.send(await Promise.all(promiseArray)); to send a string after all promise in promiseArray resolves?
Many thanks in advance.
Your callback function in router.get needs to be async:
router.get('/retrieve', async function(req, res, next) {
...
}
You can use pooling. You can implement your own pooling ffor this , or you can use a pooling library.
This possibly is not a one-liner problem..
Just for reference (maybe there are better libraries):
https://github.com/eetay/promise-pool-js
Try this:
Promise.all(promiseArray).then(values => res.send(JSON.stringify())

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client ASYNC AWAIT not Working

I am trying to verify if some data is in the session. If not the controller will redirect you to another route, to get that data.
The problem is that I am getting an error "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client"
I search over StackOverflow and I find that everyone that had this problem fix it using it async/await, but i was already using async await.
Your help will be trully appreciated!
Thank you very much!
Jose
dashboardCtrl.init = async (req, res) => {
//
var frontdata = req.session;
if (!frontdata.user) {
frontdata.user = await userfacebook.findOne({ where: { 'email': frontdata.passport.user } });
};
if (!frontdata.store) {
tmpstoredata = await userstore.findOne({ where: { 'userfacebookId': frontdata.user.id } });
if (!tmpstoredata) {
res.redirect('/toURL');
};
};
};
Note: I am using EJS MATE.
If i do this
dashboardCtrl.init = async (req, res) => {
//
res.redirect('/toURL');
};
Redirect works, the problem is using await. So i dont know how to continue
That error says that you have already sent an answer to the cliente. In other words, you are trying to declare for the second time -> **res.*****.
Check the flow again in case you have twice declared any action on express's "res".
The solution below allows you to have a good structured and readable asynchronous code.
dashboardCtrl.init = (req, res) => {
// I think destructuring looks good
let { user, store } = req.session;
(async () => {
try {
if (!user) user = await userfacebook.findOne({ where: { 'email': frontdata.passport.user } });
let tmpstoredata;
if (!store) tmpstoredata = await userstore.findOne({ where: { 'userfacebookId': frontdata.user.id } });
if (!tmpstoredata) res.redirect('/toURL');
} catch (err) {
// don't forget ;)
}
})()
};
Hope this can help you.
Greetings.
The code was OK
The problem was the EJS MATE
I replace it with EJS

Long running Node REST API takes much time to respond

I have a Rest API in node.js that takes much time to respond because it sends a request to suppliers vendor and once the response is fully prepared then it returns the result what I want is as the result is being prepared it should be able to display it on the front react side. Thanks in advance for any help and your time
here is my controller
module.exports.search = async (req, res) => {
try {
let params = req.query;
params = _.extend(params, req.body);
const result = await modules.Hotel.Search.search(req.supplierAuth, params);
res.json(result);
} catch (e) {
global.cli.log('controller:hotels:search: ', e);
res.status(500).json({ message: e.message });
}
};
here is my front side service
export const getHotels = (filters, options = {}) => {
const queryString = new URLSearchParams(options).toString();
return post(`/api/hotels/search?${queryString}`, filters);
};
The best solution is to use streams and pipe() the results as they come into express's res object, similar to this guy's approach.
You'll have to modify the modules.Hotel.Search.search(....) and make that use streams.

Purest request in KOA

I probably don't understand something about JS, but I'm having an issue writing Purest response to the page body. Like here:
var koa = require('koa')
, session = require('koa-session')
, mount = require('koa-mount')
, koaqs = require('koa-qs')
, accesslog = require('koa-accesslog')
, router = require('koa-router')()
, app = koa();
var Grant = require('grant-koa')
, grant = new Grant(require('./config.json'))
app.keys = ['grant']
app.use(accesslog())
.use(session(app))
.use(mount(grant))
.use(router.routes())
.use(router.allowedMethods());
koaqs(app)
router.get('/handle_facebook_callback', function *(next) {
getProfile(this.query.access_token);
})
var config = {
"facebook": {
"https://graph.facebook.com": {
"__domain": {
"auth": {
"auth": {"bearer": "[0]"}
}
},
"{endpoint}": {
"__path": {
"alias": "__default"
}
}
}
}
}
var request = require('request')
, purest = require('purest')({request})
, facebook = purest({provider: 'facebook', config})
function getProfile(access_token, responseToBody){
facebook.get('me')
.auth(access_token)
.request(function (err, res, body) {
this.body=JSON.stringify(body,null,2);
})
}
if (!module.parent) app.listen(3000);
console.log('oh!GG is running on http://localhost:3000/');
I would assume in facebook.request function "this.body=JSON.stringify(body,null,2);" part should write the response into the body, however it doesn't.
What exactly is the issue here?
The route (a generator) isn't waiting for getProfile to finish. You need yield.
Right now in your snippet, it executes getProfile, which returns immediately to the generator, the generator finishes, Koa sees that you haven't set this.body, so it defaults to a 404 response.
Once the callback in getProfile finally fires at some later point, the response has already been sent and you get the error.
The general solution for getting a callback-style function to work with Koa (i.e. making it so you can yield it) is to wrap it in a Promise:
function getProfile (access_token) {
return new Promise(function (resolve, reject) {
facebook.get('me')
.auth(access_token)
.request(function (err, res, body) {
if (err) return reject(err)
resolve(body)
})
})
}
router.get('/handle_facebook_callback', function * (next) {
const profile = yield getProfile(this.query.access_token)
this.type = 'application/json'
this.body = JSON.stringify(profile, null, 2)
})
getProfile now returns a Promise which you can yield.
Also, notice that I changed it so that getProfile resolves with the profile object, and the Koa handler is the one that stitches together this.body and the JSON.
This is generally how you want to do things in Koa so that all of your response mutation happens inside the handler in one place.

Categories

Resources