How to add a new then after fetch has run - javascript

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.

Related

can you await a function not defined as async but declared new Promise inside of it?

We have this in our code:
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
new Promise(resolve => {
const insertStatus = {
....
};
if (!recommendationsArray.length > 0) {
resolve({
error: false,
results: insertStatus,
localMessage: 'recommendationsArray.length = 0',
});
}
insertStatus.arrayEmpty = false;
dbConfig.transaction(txn => {
for (let i = 0; i < recommendationsArray.length; i++) {
const {
id,
recommendation_attribute,
recommendation_value,
segment_id,
target_attribute,
target_value,
is_deleted,
updated_at,
name,
image,
border_color,
} = recommendationsArray[i];
const params = [
id,
recommendation_attribute,
recommendation_value,
segment_id,
target_attribute,
target_value,
is_deleted,
updated_at,
name,
image,
border_color,
];
try {
txn.executeSql(
upsertRecommendations,
params,
(_tx, _results) => {
resolve({ error: false, results: insertStatus });
},
error => {
crashlytics().recordError(error);
if (__DEV__) {
console.log('sqlUpsertRecommendations error:', error);
}
const { notInserted } = insertStatus;
insertStatus.inserted = notInserted + 1;
if (1 + i === recommendationsArray.length) {
resolve({ error: false, ...error, results: insertStatus });
}
},
);
} catch (e) {
crashlytics().recordError(e);
}
}
});
});
};
I am learning async await and people in our team are using it like
const testFunction = async () => {
....
await sqlUpsertRecommendations(parsedData)
But vscode is saying
'await' has no effect on the type of this expression.
I just need idea how to best approach and use this kind of code?
You need to actually return the new Promise, then you can await it. If you don't, you just start a Promise and then immediately return undefined, and you can't await undefined.
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
return new Promise(resolve => {
// ... rest of code
async is really just syntactic sugar for creating and using Promises. An async function just returns a new Promise and that's what await cares about, so await will work if you have a normal function that returns a Promise.

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.

keep looping a function until the callback inside resolves to true

I've the two interconnected functions.
module.exports = async function newRunner() {
console.log('No Config found. Requesting new Runner ID');
if (!process.env.ADDRESS) {
const a = await inquirer.prompt([
{
type: 'input',
message: 'Enter the runner api address',
name: 'address',
},
]);
config.ADDRESS = a.address;
} else {
config.ADDRESS = process.env.ADDRESS;
}
config.save();
const res = await auth.checkAddress();
console.log('New Runner ID', res.id);
config.ID = res.id;
config.TOKEN = res.token;
config.save();
};
The line const res = await auth.newRunner(); call the following function.
exports.checkAddress = args => new Promise((resolve, reject) => {
const cArgs = {
data: args,
path: {},
parameters: {},
headers: {
'Content-Type': 'application/json',
},
};
client.post(`${config.ADDRESS}/new-runner`, cArgs, (data, response) => {
if (response.statusCode !== 200) return reject(new Error(`Status code ${response.statusCode}`));
return resolve(data);
})
.on('responseTimeout', (res) => {
console.log('Update Response Timeout!', res);
reject(new Error('response has expired'));
})
.on('error', (err) => {
reject(err);
});
});
I need to keep the first function running until the address entered is correct and the second function resolves to true. I've tried the following.
await newRunner();
while (!await auth.newRunner()) {
await newRunner();
}
The functions runs as it should if I enter the correct address, wrong address breaks it, but I want it to keep looping until the address is correct. What do I need to change do archive the correct behaviour?
Your "main" newRunner (at the top of your question) only fulfills when auth.newRunner fulfills, and rejects otherwise. auth.newRunner only fulfills when it successfully got data. So to loop until you've successfully got data, you need to wrap the call in a try/catch:
let failed;
do {
try {
await newRunner();
failed = false;
} catch {
failed = true;
}
} while (failed);
Might be best to put some limits on that, though, so it can't cycle infinitely.

Confused between promise and async/awaits

I'm a novice in Node JS. I practice promise and I successfully used it. What I understand is with using a promise you can hold the output and send resolve and reject. I used in database operation.
Then someone suggested me to use async/awaits. So here is my code which ran successfully the first time.
shop.js file
const models = require("../models");
const shopModel = models.Shop;
exports.checkShop = function(shopName) {
return new Promise((reslove, reject) => {
shopModel
.findOne({ where: { shop: shopName } })
.then(rs => {
if (rs) {
reslove(rs);
}
})
.catch(err => {
reject(err.toString());
});
});
};
And the file where i called this
const shopController = require("./shop");
exports.getInstall = function(req, res) {
const shop = req.body.shop;
if (!cn(shop)) {
shopController
.checkShop(shop)
.then(
shopCheck =>
function() {
if (shopCheck) {
res.send(`Welcome back ${shopCheck.shop}`);
} else {
//my else stuff
}
}
)
.catch(
e =>
function() {
res.state(500).send(e);
}
);
} else {
return res
.status(400)
.send(
"Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request"
);
}
};
And this is how I tried to replace it with async/awaits. But it does not work.
exports.checkShop = async function(shopName) {
try{
var rs = await shopModel.findOne({ where: { shop: shopName } });
if(rs){
return rs;
}
else{
return false;
}
}
catch(e){
return Promise.reject(new Error(400));
}
};
And the other file
exports.getInstall = function(req, res) {
const shop = req.body.shop;
if (!cn(shop)) {
var shopCheck = shopController.checkShop(shop);
try {
if (shopCheck) {
res.send(`Welcome back ${shopCheck.shop}`);
} else {
// else stuff
}
} catch (e) {
res.state(500).send(e);
}
} else {
return res
.status(400)
.send(
"Missing shop parameter. Please add ?shop=your-development-shop.myshopify.com to your request"
);
}
};
Every function with the async keyword before it will (explicitly or implicitly) return a promise.
So when you call shopController.checkShop you will either have to do something like
shopController.checkShop().then(.... )
or make getInstall an async function as well so that you can use await inside it.
exports.getInstall = async function(req, res) {
// other code here..
const result = await shopController.checkShop(shop);
//..
}
Edit:
If you want to make getInstall async and use await on checkShop you will have to catch the potential rejection using try {} catch like you did in checkShop.

Post request to wait a function

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;
});
}

Categories