Dynamic js bundle synchronous api call - javascript

My case is I have a js web app that I bundle with webpack. The app is deployed to a azure webapp where I like to make use of the Application Settings feature. Particular I like to be able to set the api base url the app should use. To make that work my idea is to place a .php in the root which simple return something like {url: 'api.mypage.com'}. The .php can read the evironment variable and return correct url based on the environment.
Today when I bundle my js it reads the .config with the api url which makes the bundle environment dependent. I have been trying to make a call relative to it self (/api.php) that works fine. The problem is a can't get it working synchronously.
How would you guys solve this? I'm not a js expert but there must be a smart solution fot this. I have been fibbleing with somthing like this:
const dynConfig = {
apiUrl: getApiBaseUrl((param) => {console.log('3. Callback done: ', param); return param;})
}
function getApiBaseUrl(_callBack) {
console.log('1. Enter getApiBaseUrl');
fetch('https://official-joke-api.appspot.com/jokes/programming/random')
.then(response => response.json())
.then( data => {
console.log('2. Data fetched: ', data[0].type);
_callBack(data[0].type);
})
.catch(err => {
console.error(err);
})
}
// This where it fails to "wait"
console.log('4. This should not be undefined: ', dynConfig.apiUrl);
jsfiddle

As per our discussion in the comments here is an implementation with both async/await and promises
Async/Await based
/* Async/Await */
async function async_getApiBaseUrl() {
try {
let res = await (await fetch('https://official-joke-api.appspot.com/jokes/programming/random')).json();
return res[0];
} catch(err) {
console.error(err);
return '';
}
}
async_getApiBaseUrl().then(function(joke){
console.log('async joke:')
console.log(`Got a joke: ${joke.setup}`);
console.log(joke.punchline);
console.log(' ');
});
Promise based
/* Promise */
function promise_getApiBaseUrl(_callBack){
return new Promise(function(resolve, reject) {
fetch('https://official-joke-api.appspot.com/jokes/programming/random')
.then(async function(res) { return await res.json(); })
.then(function (res) {
resolve(res[0]);
})
.catch(function (err) { reject(err) });
});
}
promise_getApiBaseUrl()
.then(function(joke) {
// use your apiBaseUrl
console.log('promise joke:')
console.log(`Got a joke: ${joke.setup}`);
console.log(joke.punchline);
})
.catch(function(err){
console.error(err);
});

Related

Async and Await not working with Axios React

I am trying to test my react project locally with my computer and with my phone. I am using JavaScript not TypeScript.
When I run the project on my computer everything works fine, but when I try to load it on my phone, I get an error: Unhandled Rejection (TypeError): undefined is not an object (evaluating 'scheduleArr.forEach'). I thought I was using async and await correctly because this code workes on my computer. I'm confused as to why this code works on one platform but not the other.
async function getSchedule() {
let scheduleArr = await axios.get('api/schedule/')
.then(response => {
return response.data;
})
.catch((error) => {
console.log(`ERROR: ${error}`);
});
scheduleArr.forEach(game => {
/* do stuff */
}
});
I think this problem is directly related to async and await because when I comment out this function, my project loads correctly on my phone.
Can anyone help me understand what I'm doing wrong?
You can't use the async/await pattern with then. Either use it like :
async function getSchedule() {
try {
let scheduleArr = await axios.get("api/schedule/");
console.log(scheduleArr.data);
} catch (err) {
console.log(`ERROR: ${err}`);
}
scheduleArr.forEach(game => {
/* do stuff */
});
}
Or with the default pattern :
function getSchedule() {
axios
.get("api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
Note that I moved your foreach loop into the then. It is asynchronous and therefore need to be triggered only when you get the result of your api call.
I figured out what the issue was. It had nothing to do with async await or axios.
This was my code before
function getSchedule() {
axios
.get("http://localhost:5000/api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
I changed my API call to use my actual local IP instead of localhost
function getSchedule() {
axios
.get("http://192.168.X.XX:5000/api/schedule/")
.then(response => {
let scheduleArr = response.data;
// Do this inside the 'then'
scheduleArr.forEach(game => {
/* do stuff */
});
})
.catch(error => {
console.log(`ERROR: ${error}`);
});
}
Nesting my code in the .then block did fix my undefined error even with the bad url. Thank you for that suggestion #Quentin Grisel.
Fixing my url let me test it on different devices.

Firebase Cloud Function problem - promises related

I have a problem with a callable firebase cloud function (exports.listAllUsers).
Backend: Nodejs & Firebase-Cloud_functions to use admin.auth().listUsers
Problem: Result (usersList; an array with the uid of the users) is OK in cloud-functions-emulator (log) but NOT in the client (console.log(usersList) in browser is null)
Maybe a problem related with...: bad understanding of promises. A second code example is working with async/await but not working with .then().
The code of the function listAllUsers is basically copy&paste from docs (original code snipet: https://firebase.google.com/docs/auth/admin/manage-users#list_all_users). My code modifications are 5 (comments in the code), to get a list of users uid:
exports.listAllUsers = functions.https.onCall(() => { // -1) callable function
const usersList = ['12323211'] // ----------------------2) first user uid, just a sample
function listAllUsers (nextPageToken) {
// List batch of users, 1000 at a time.
admin.auth().listUsers(1000, nextPageToken)
.then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
usersList.push(userRecord.uid) // --------------3) get users uids
})
if (listUsersResult.pageToken) {
// List next batch of users.
listAllUsers(listUsersResult.pageToken)
}
console.log(usersList) //-------------------------4) list users uid (cloud functions console)
return usersList //-------------------------------5) return to the client the same as showed at the console
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
}
// Start listing users from the beginning, 1000 at a time.
listAllUsers()
})
The method in the client is...
getUsersList: async function (userType) {
const usersList = await this.$fb.functions().httpsCallable('listAllUsers')()
console.log('usersList: ', usersList)
}
I am using firebase emulators. Cloud functions log is OK, you can see the sample uid and the other uids:
cloud function emulator console output
But I don't get the same in the client:
client console output
I think I am doing something wrong related with promises... because a simplification of the code is working with async/await:
exports.listAllUsers = functions.https.onCall(async () => {
try {
listUsersResult = await admin.auth().listUsers()
return listUsersResult
} catch (error) {
console.log('Error listing users:', error)
return null
}
})
Browser console output (reduced code with async/await)
But the same is not working with then()...
exports.listAllUsers = functions.https.onCall(() => {
admin.auth().listUsers()
.then((listUsersResult) => {
return listUsersResult
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
})
Browser console output (reduced code with .then())
I can refactor the initial snipet of code with async/await, but I am interested in the solution with the original code (.then() flavor; I always use async/await because I am quite new at js)... Anyone can help me? Thanks!
This is because with the async/await version you correctly return listUsersResult by doing
listUsersResult = await admin.auth().listUsers()
return listUsersResult
but, with the then version, you don't. You should return the entire promises chain, as follows:
exports.listAllUsers = functions.https.onCall(() => {
return admin.auth().listUsers() // !!! Note the return here !!!
.then((listUsersResult) => {
return listUsersResult
})
.catch((error) => {
console.log('Error listing users:', error)
return null
})
})
Finally I decided to code an async/await version for my cloud function. The then version in the first code snipet requires more than just adding the return to the entire promises chain (it initially complains because of the recursivity, maybe, asking me to add asyncto the wrapper function listAllUsers... I'd like the then version to just copy&paste from the firebase docs, but it wanted more).
I'd like to share this homemade (but initially tested) version as a sample with async/await without recursivity to list users with admin.auth().listUsers(maxResults?, pageToken?):
// get list of users
exports.listAllUsers = functions.https.onCall(async () => {
const usersList = []
try {
let listUsersResult
let nextPageToken
do {
if (listUsersResult) {
nextPageToken = listUsersResult.pageToken
}
// eslint-disable-next-line no-await-in-loop
listUsersResult = await admin.auth().listUsers(1000, nextPageToken)
listUsersResult.users.forEach((userRecord) => {
usersList.push(userRecord.uid)
})
} while (listUsersResult.pageToken)
return usersList
} catch (error) {
console.log('Error listing users:', error)
return null
}
})

NodeJS NPM soap - how do I chain async methods without callbacks (ie use async or Promise)?

I have successfully called a sequence of soap webservice methods using nodejs/javascript, but using callbacks... right now it looks something like this:
soap.createClient(wsdlUrl, function (err, soapClient) {
console.log("soap.createClient();");
if (err) {
console.log("error", err);
}
soapClient.method1(soaprequest1, function (err, result, raw, headers) {
if (err) {
console.log("Security_Authenticate error", err);
}
soapClient.method2(soaprequest2, function (err, result, raw, headers) {
if (err) {
console.log("Air_MultiAvailability error", err);
}
//etc...
});
});
});
I'm trying to get to something cleaner using Promise or async, similar to this (based on the example in the docs here https://www.npmjs.com/package/soap) :
var soap = require('soap');
soap.createClientAsync(wsdlURL)
.then((client) => {
return client.method1(soaprequest1);
})
.then((response) => {
return client.method2(soaprequest2);
});//... etc
My issue is that in the latter example, the soap client is no longer accessible after the first call and it typically returns a 'not defined' error...
is there a 'clean' way of carrying an object through this kind of chaining to be used/accessible in subsequent calls ?
Use async/await syntax.
const soap = require('soap');
(async () => {
const client = await soap.createClientAsync(wsdlURL);
cosnt response = await client.method1Async(soaprequest1);
await method2(soaprequest2);
})();
Pay attention to Async on both createClient and method1
In order to keep the chain of promises flat, you can assign the instance of soap to a variable in the outer scope:
let client = null;
soap.createClientAsync(wsdlURL)
.then((instance) => {
client = instance
})
.then(() => {
return client.method1(soaprequest2);
})
.then((response) => {
return client.method2(soaprequest2);
});
Another option would be nested chain method calls after the client is resolved:
soap.createClientAsync(wsdlURL)
.then((client) => {
Promise.resolve()
.then(() => {
return client.method1(soaprequest2);
})
.then((response) => {
return client.method2(soaprequest2);
});
})

Refactoring Complicated Nested Node.js Function

I have the following snippet of code below. It currently works, but I'm hoping to optimize/refactor it a bit.
Basically, it fetches JSON data, extracts the urls for a number of PDFs from the response, and then downloads those PDFs into a folder.
I'm hoping to refactor this code in order to process the PDFs once they are all downloaded. Currently, I'm not sure how to do that. There are a lot of nested asynchronous functions going on.
How might I refactor this to allow me to tack on another .then call before my error handler, so that I can then process the PDFs that are downloaded?
const axios = require("axios");
const moment = require("moment");
const fs = require("fs");
const download = require("download");
const mkdirp = require("mkdirp"); // Makes nested files...
const getDirName = require("path").dirname; // Current directory name...
const today = moment().format("YYYY-MM-DD");
function writeFile(path, contents, cb){
mkdirp(getDirName(path), function(err){
if (err) return cb(err)
fs.writeFile(path, contents, cb)
})
};
axios.get(`http://federalregister.gov/api/v1/public-inspection-documents.json?conditions%5Bavailable_on%5D=${today}`)
.then((res) => {
res.data.results.forEach((item) => {
download(item.pdf_url).then((data) => {
writeFile(`${__dirname}/${today}/${item.pdf_file_name}`, data, (err) => {
if(err){
console.log(err);
} else {
console.log("FILE WRITTEN: ", item.pdf_file_name);
}
})
})
})
})
.catch((err) => {
console.log("COULD NOT DOWNLOAD FILES: \n", err);
})
Thanks for any help you all can provide.
P.S. –– When I simply tack on the .then call right now, it fires immediately. This means that my forEach loop is non-blocking? I thought that forEach loops were blocking.
The current forEach will run synchronously, and will not wait for the asynchronous operations to complete. You should use .map instead of forEach so you can map each item to its Promise from download. Then, you can use Promise.all on the resulting array, which will resolve once all downloads are complete:
axios.get(`http://federalregister.gov/api/v1/public-inspection-documents.json?conditions%5Bavailable_on%5D=${today}`)
.then(processResults)
.catch((err) => {
console.log("COULD NOT DOWNLOAD FILES: \n", err);
});
function processResults(res) {
const downloadPromises = res.data.results.map((item) => (
download(item.pdf_url).then(data => new Promise((resolve, reject) => {
writeFile(`${__dirname}/${today}/${item.pdf_file_name}`, data, (err) => {
if(err) reject(err);
else resolve(console.log("FILE WRITTEN: ", item.pdf_file_name));
});
}))
));
return Promise.all(downloadPromises)
.then(() => {
console.log('all done');
});
}
If you wanted to essentially block the function on each iteration, you would want to use an async function in combination with await instead.

react native fetch not calling then or catch

I am using fetch to make some API calls in react-native, sometimes randomly the fetch does not fire requests to server and my then or except blocks are not called. This happens randomly, I think there might be a race condition or something similar. After failing requests once like this, the requests to same API never get fired till I reload the app. Any ideas how to trace reason behind this. The code I used is below.
const host = liveBaseHost;
const url = `${host}${route}?observer_id=${user._id}`;
let options = Object.assign({
method: verb
}, params
? {
body: JSON.stringify(params)
}
: null);
options.headers = NimbusApi.headers(user)
return fetch(url, options).then(resp => {
let json = resp.json();
if (resp.ok) {
return json
}
return json.then(err => {
throw err
});
}).then(json => json);
Fetch might be throwing an error and you have not added the catch block. Try this:
return fetch(url, options)
.then((resp) => {
if (resp.ok) {
return resp.json()
.then((responseData) => {
return responseData;
});
}
return resp.json()
.then((error) => {
return Promise.reject(error);
});
})
.catch(err => {/* catch the error here */});
Remember that Promises usually have this format:
promise(params)
.then(resp => { /* This callback is called is promise is resolved */ },
cause => {/* This callback is called if primise is rejected */})
.catch(error => { /* This callback is called if an unmanaged error is thrown */ });
I'm using it in this way because I faced the same problem before.
Let me know if it helps to you.
Wrap your fetch in a try-catch:
let res;
try {
res = fetch();
} catch(err) {
console.error('err.message:', err.message);
}
If you are seeing "network failure error" it is either CORS or the really funny one, but it got me in the past, check that you are not in Airplane Mode.
I got stuck into this too, api call is neither going into then nor into catch. Make sure your phone and development code is connected to same Internet network, That worked out for me.

Categories