Post request to wait a function - javascript

I know the question is asked really often, and I might get downvoted for that. But I really struggle to understand how I can wait for a function to process data before returning a value.
I had a look on many popular posts (such as here), but I cannot achieve what I want.
Here is my code:
app.post("/video_url", function (req, res) {
videoProcessed = videoScraper(req.body.videoURL.videoURL);
res.send(videoProcessed);
});
It does not wait for this function to process the data:
function videoScraper(url) {
console.log("URL to Scraper: " + url);
const options = {
uri: `${url}`,
transform: function(body) {
return cheerio.load(body);
}
};
var videoProcessed;
rp(options)
.then(($) => {
videoProcessed = $("body").find("iframe").attr("src");
return videoProcessed;
})
.catch((err) => {
console.log(err);
});
}
I tried using callbacks but it gets really messy, and I don't know were to put the promise (if any) in my code.

Add await and async (if you have node 8+):
app.post("/video_url", async function (req, res) {
const videoProcessed = await videoScraper(req.body.videoURL.videoURL);
res.send(videoProcessed);
});
And in your videoScraper function, you need to return rp! :
function videoScraper(url) {
console.log("URL to Scraper: " + url);
const options = {
uri: `${url}`,
transform: function(body) {
return cheerio.load(body);
}
};
return rp(options)
.then($ => $("body").find("iframe").attr("src"))
.catch((err) => {
console.error(err);
});
}
That would depend on the videoScrapper working fine, I've no idea what rp is so I can't tell.
Don't forget to handle videoProcessed === undefined (error case) in the first code snippet. It can also be abstracted using express-promise-router that will even catch async errors... That's further down the road.
Don't hesitate to read up on await & async, it's really wonderful to write asynchronous code in the same manner as synchronous code.

Use async/await
app.post("/video_url", async (req, res)=> {
try{
let videoProcessed = await videoScraper(req.body.videoURL.videoURL);
res.send(videoProcessed);
}
catch(ex){
// handle the exception
}
});
const videoScraper = async fuction(url) {
console.log("URL to Scraper: " + url);
let options = {
uri: `${url}`,
transform: function(body) {
return cheerio.load(body);
}
};
try{
let temp = await rp(options);
let videoProcessed = $("body").find("iframe").attr("src");// you can return it directly
return videoProcessed;
}
catch(ex){
// handle the exception
}
}
if you your node is < 8 then use promises (bluebird module)
const bluebird = require('bluebird');
function videoScraper(url){
return new bluebird(function(resolve,reject){
let options = {
uri: `${url}`,
transform: function(body) {
return cheerio.load(body);
}
};
rp(options)
.then(($)=>{
resolve($("body").find("iframe").attr("src"));
})
.catch(err=>{
return err;
})
})
}
app.post("/video_url", (req, res)=> {
videoScraper(req.body.videoURL.videoURL)
.then(result=>{
res.send(result)
})
.catch(err=>{
// handle the error
})
});
Do not use const for variable declaration unless its value is constant, and usually use let instead of var

You can try the following:
app.post("/video_url", function (req, res) {
videoScraper(req.body.videoURL.videoURL)
.then(videoProcessed => {
res.send(videoProcessed);
})
.catch(err => {
// render proper response with error message
})
});
And change the function to something as below, so as to return a promise from the same:
function videoScraper(url) {
console.log("URL to Scraper: " + url);
const options = {
uri: `${url}`,
transform: function(body) {
return cheerio.load(body);
}
};
return rp(options)
.then(($) => {
videoProcessed = $("body").find("iframe").attr("src");
return videoProcessed;
});
}

Related

How can I "encapsulate" this code into a module so it could become reusable?

I have got this Node.JS snippet and would like to write it as a module, so I can use recaptcha in different parts of my system.
This is how it currently looks like:
app.post('/register_user', (req, res) => {
const secret_key = process.env.RECAPTCHA_SECRET;
const token = req.body.recaptcha;
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
fetch(url, { method: "post",})
.then((response) => response.json())
.then((google_response) => {
if (google_response.success == true) {
res.format({'text/html': () => res.redirect(303, '/register'),})
} else {
return res.send({ response: "Failed" });
}
})
.catch((error) => {
return res.json({ error });
});
})
I have tried to write the following module which works absolutely great, but I have absolute no idea about how to call it from the app.post, since I always get undefined as return:
import fetch from 'node-fetch';
export function fetch_out(url, timeout = 7000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), timeout)
)
]);
}
export async function checkRecaptcha(token, secret_key){
const url = "https://www.google.com/recaptcha/api/siteverify?secret=" + secret_key + "&response=" + token;
try{
const response = await fetch_out(url, 1000);
const google_response = await response.json();
}catch(error){
return error;
}
return google_response;
}
Any help would be appreciated! Thanks!
You could make this method reusable by removing the framework actions that need to happen and only return if the validation was successful or not. This way, it will be reusable in another project that doesn't use a specific framework.
Example module;
export async function checkRecaptcha(token, secret_key) {
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
const response = await fetch(url, { method: "post",});
if (!response.ok) return false;
const json = await response.json();
if (!json.success) return false;
return true;
}
Usage:
import { checkRecaptcha } from "./some-file-name";
app.post('/register_user', async (req, res) => {
const isHuman = await checkRecaptcha(req.body.recaptcha, process.env.RECAPTCHA_SECRET);
if (!isHuman) {
return res.send({ response: "Failed" });
}
return res.format({'text/html': () => res.redirect(303, '/register'),});
});
If you specifically want to call an action after the validation, you can also use successful and error callbacks.

How to use async, await and promises?

I am building a web scraper to get all of user's submissions on codeforces.
I don't know much about async, await, promises.
I have used axios (promise based) to request codeforces and cheerio to parse HTML .
app.post("/", (req, res) => {
const usernameorhandle = req.body.userName;
getstatus(usernameorhandle).then ( ()=> {
var output = fs.createWriteStream(__dirname + '/Data/solutions.zip');
var archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
});
output.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
output.on('end', function() {
console.log('Data has been drained');
});
res.attachment(__dirname + "/Data/Problems", 'Codeforces-Solutions');
archive.pipe(res);
archive.directory(__dirname + "/Data/Problems", 'Codeforces-Solutions');
archive.finalize();
}) })
I am using to accept post request.
I am putting all the solutions into a folder and creating zip folder and then send to res.
Below is my getstatus function.
async function getstatus(handle){
return new Promise(async (resolve, reject)=> {
console.log("HELLLLLLLOOOOOOOO");
await axios.get("https://codeforces.com/api/user.status?handle=" + handle + "&from=1")
.then(response => {
if(response.data.status === 'OK'){
let results = response.data.result;
console.log("AAAAAAAAAAAAAAAAAAAAAAAa");
scrape(results).then( () =>{
console.log("DONE");
resolve();
})
.catch(err => console.log(err));
// resolve();
}
else console.log(submissions.comment);
})
})
}
I use scrape function to obtain HTML data and put to folder named Problems.
async function scrape (results){
console.log("inside scrape");
// console.log("HELLO");
return new Promise( async (resolve, reject) => {
await results.forEach(async (result)=> {
if(result.verdict === 'OK'){
await axios.get("https://codeforces.com/contest/" + result.contestId + "/submission/" + result.id)
.then(solutionPage => {
const $ = cheerio.load(solutionPage.data);
const path = "/home/srujan/Desktop/crawlerapp/Data/Problems/" + result.problem.name + ".cpp";
fs.writeFile(path, $('#program-source-text').text(), function(err){
if(err){
console.log(err);
}
else{
console.log("Saved file");
}
})
})
.catch( error => {
console.log("HTML PARSE ERROR" + error);
})
}
})
console.log("hey");
resolve();
})
The problem is I am getting
HELLLLLLLOOOOOOOO
AAAAAAAAAAAAAAAAAAAAAAAa
inside scrape
hey
DONE
saved file
saved file
...
Browser downloads after DONE and then files are saved.
I am new to js and don't know why I am getting this.
PS : I know this is very long question. I tried reading a lot about this. Didn't understand properly how to do that. I copy pasted some code which I didn't understand like how to zip a folder.
forEach(callback) executes callback. If callback returns a promise (ie, it's an async function), the promise won't be resolved before calling the callback on the next element of the array.
So, basically, you can't use async functions inside forEach... But you can use for-loops or Promise.all instead!
Also, fs.writeFile works with sync + callback, but there exists a fs.promise.writeFile that uses promises instead.
Here's a scrape function that should work better:
async function scrape(results) {
for (const result of results) {
if(result.verdict === 'OK') {
const solutionPage = await axios.get("https://codeforces.com/contest/" + result.contestId + "/submission/" + result.id);
const $ = cheerio.load(solutionPage.data);
const path = "/home/srujan/Desktop/crawlerapp/Data/Problems/" + result.problem.name + ".cpp";
try {
await fs.promises.writeFile(path, $('#program-source-text').text());
} catch(err) { console.log(err) }
}
}
}
The problem is to use result.forEach
Try to use a simple for(let i = 0; i < result.length; i++) without async.
If that doesn't work, try to return anything inside the then.
This is how I would construct getstatus function with await async
async function getstatus(handle) {
const response = await axios.get("https://codeforces.com/api/user.status?handle=" + handle + "&from=1")
if(response.data.status === 'OK') {
let results = response.data.result;
try {
await scrape(results);
console.log("DONE");
}
catch(error) {
}
}
}
and scrape function accordingly...
const fs = require('fs').promises;
async function scrape (results) {
results.forEach(async (result)=> {
if(result.verdict === 'OK') {
const solutionPage = await axios.get("https://codeforces.com/contest/" + result.contestId + "/submission/" + result.id)
const $ = cheerio.load(solutionPage.data);
const path = "/home/srujan/Desktop/crawlerapp/Data/Problems/" + result.problem.name + ".cpp";
try {
await fs.writeFile(path, $('#program-source-text').text())
console.log("Saved file");
}
catch(error) {
}
}
}
}

How to add a new then after fetch has run

I have a method that runs a fetch request and then saves the result or error like this:
saveTema() {
this.gateway.editTema(this.state.tema)
.then(tema => {
this.setState({
tema,
error: null,
isDirty: false,
});
})
.catch(httpOrOtherError => {
if (httpOrOtherError.status) {
if (httpOrOtherError.status === 400) {
httpOrOtherError.json().then(result => {
const serverValidationfailures =
this.transformValideringsfeil(result.valideringsfeil);
this.setState({
error: {
valideringsfeil: {...serverValidationfailures},
},
showActivationDialog: false,
})
});
} else {
this.setState({
error: {httpError: {status: httpOrOtherError.status, statusText: httpOrOtherError.statusText}},
showActivationDialog: false,
});
}
} else {
this.setState({
error: {fetchReject: {message: httpOrOtherError.message}},
showActivationDialog: false,
})
}
})
}
And this is the fetch request itself:
editTema(tema) {
return fetch(
this.temaUrl(tema.id),
{
method: 'PUT',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(tema)
})
.then(res => {
if (res.ok) {
return res.json();
}
throw res;
}
);
}
I would like to run this method from another one, and check if everything went ok with this method and based on that do further actions. Something like this:
this.saveTema().then(() => {
this.props.history.push({
pathname: '/tema',
state: {
successMessage: `Tema ${this.state.tema.id} ble oppdatert`,
}
}}));
But, this is of course wrong, I am not sure how can I do this, to run some code after the fetch handling of the fetch request has finished. What is the right way to do it?
saveTema() {
return this.gateway.editTema(this.state.tema)
...
Return the promise and then you'll be able to do exactly what you are trying to do.
Return the editThema result after setting up the handlers:
saveTema() {
let prom = this.gateway.editTema(this.state.tema)
prom.then(tema => {
// .. success handling code
})
.catch(httpOrOtherError => {
// .. error handling code
})
return prom;
}
Now you can call your function exactly like you wanted to.
You can achieve that by two approaches
Using async/await
Using native Promise
1. async/await way
userController.js
const userUtils = require('./userUtils');
const userCtr = {};
userCtr.searchUser = async (req, res) => {
try {
const { userName } = req.query;
const result = await userUtils.searchUser(userName);
return res.status(200).json(result);
} catch (err) {
return res.status(err.code).json({ error: err.error });
}
};
module.exports = userCtr;
userUtils.js
const userUtils = {};
userUtils.searchUser = async (userName) => {
try {
if (userName) {
// ...Do some cool stuff
const result = [];
return result;
}
const errorObj = { code: 400, error: 'ERR_VALID_PARAM' };
throw errorObj;
} catch (err) {
console.error(err);
throw err;
}
};
module.exports = userUtils;
2. Promise way
userController.js
const userUtils = require('./userUtils');
const userCtr = {};
userCtr.searchUser = (req, res) => {
const { userName } = req.query;
userUtils.searchUser(userName)
.then((result) => {
return res.status(200).json(result);
})
.catch((err) => {
return res.status(err.code).json({ error: err.error });
});
};
module.exports = userCtr;
userUtils.js
const userUtils = {};
userUtils.searchUser = (userName) => {
return new Promise((resolve, reject) => {
if (userName) {
// ...Do some cool stuff
const result = [];
return resolve(result);
} else {
const error = { code: 400, error: 'Please provide valid data!' }
return reject(error);
}
});
};
module.exports = userUtils;
In both approaches you can hold further execution (in both approach Promise are used directly or indirectly), In a second approach you can achieve by .then().catch() whereas in the first approach just you need to put a keyword await and put async on your function, I suggest you to use async/await. Because when you need to wait for the completion of more than 3 promises and yo go with Native Promise then your code will be so messy like .then().then().then() Whereas in a first approach you just need to put a keyword await on starting of your function, Using async/await approach your code will neat and clean and easily understandable and easy to debug.

Promise returns wrong value

In my code I try to assign a value to json variable to return it after (because I can't return it from the anon. function).
As my function is async, because it sends requests (maybe someone knows how to make it sync? I didn't plan to make it asynchronous), I've added await before the request (https.get).
I've been trying to get value from the Promise, but it's always undefined, even though I've awaited the async function.
Here's a code:
async function get_users() {
const https = require('https');
var token = '...';
var json = undefined;
await https.get('...', (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
json = JSON.parse(data)['response']['items'];
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
return json;
}
get_users().then(function(result) {
console.log(result);
});
Return a Promise and resolve it, when the end event is called, otherwise reject it in case of an error occurred:
async function get_users() {
const https = require('https');
const token = '...';
return new Promise((resolve, reject) => {
https.get('...', resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
let json;
try {
json = JSON.parse(data)['response']['items'];
} catch (e) {
reject(e);
};
resolve(json);
});
}).on("error", err => reject(err));
});
}
get_users().then(result => console.log(result));
Please refer my below code.I had issues with getting responses from Promises too.But i finally got it to work.Here's the code:
var output;
var rp = require('request-promise-native');
var myJSONObject = {
"inputs": [{
"name": "<name>",
"value": < value >
}]
};
var orchName = 'TEST05';
postData = JSON.stringify(myJSONObject);
return networkCall(postData, orchName).then((response) => {
console.log('response is' + response)
}).catch((response) => {
console.log(`ERROR: ` + response);
});
function networkCall(postData, orchName) {
return new Promise((resolve, reject) => {
var options = {
method: 'post',
uri: '<URL>',
body: postData,
auth: {
'user': 'usr',
'pass': 'pwd'
},
json: true
};
return rp(options)
.then(body => {
var response = body;
resolve(response);
})
.catch(err => {
console.log('FAILED' + err);
reject(err);
});
});
}
This way your code can run in Synchronous Flow.If the return value is undefined,then,what might have probably happened is that the calling function would have finished executing even before the called function returns its response.But the above approach would work just fine.

REST API calls using async await

Here's my code snippet
var clients = require('restify-clients');
async function callApi(val){
const client = clients.createJsonClient({ url: apiUrl });
await client.get('/my/url', (err, req, res, obj) => {
if (err) {
return err;
} else {
return obj;
}
});
}
I've tried a few ways of calling it, but they all aren't working
First way:
var results = await callApi(val);
Second way:
var results = callApi(val).then(data => {
console.log(data);
})
client.get doesn't return a promise, you can't use await on a function which doesn't return a promise (honestly you can, but it doesn't make sense). The correct solution here is to promisify client.get and return a promise:
function callApi(val) {
const client = clients.createJsonClient({ url: apiUrl });
return new Promise((resolve, reject) => {
client.get('/my/url', (err, req, res, obj) => {
if (err) {
reject(err);
} else {
resolve(obj);
}
});
});
}
// Usage
let results = await callApi(val);
Try to remove await from results
var results = callApi(val);

Categories