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.
Related
I know this can be solved by writing all codes to async-await style, then can simply write let text = await res.text(); then try catch the JSON.parse(text) and then do decision.
But here I just want to know if there is any way we can achieve that in .then/.catch style.
Consider the below code:
async function test() {
try {
let n = await fetch("https://stackoverflow.com")
.then(res => {
return res.json()
})
.then(data => data.results.length)
.catch(e => {
console.error("Catch 2", e)
})
}
catch (e) {
console.error("Catch 3", e)
}
}
if we execute this function in the browser devtools(F12) with await test(), then there will be an error catch by the "Catch 2" clause. But in the error detail we can only see some logs like JSON parse error.
We cannot see the full text of the response body.
Is there any way that can get the text when the JSON parsing failed?
Your best bet is to look at the response in your devtools' network tab. That will show you the full response.
But if you want to do it in code, you can separate reading the response from parsing it by using the text method instead of the json method, then parsing the text yourself.
The parsing error may be down to the fact you aren't checking for HTTP success. As I noted on my old anemic blog here, fetch only rejects its promise on network errors, not HTTP errors (like 404, 500, etc.). To check for HTTP success, look at the ok or status properties.
Here's the minimal-changes version separating reading the response from parsing it, and checking for HTTP success before reading it at all:
async function test() {
try {
let n = await fetch("https://stackoverflow.com")
.then((res) => {
if (!res.ok) { // ***
throw new Error(`HTTP error ${res.status}`); // ***
} // ***
return res.text(); // ***
})
.then((text) => {
// *** you can look at `text` here in a debugger, or
// *** log it, save it, etc., before parsing below
// *** (which might throw an error)
try {
const data = JSON.parse(text); // ***
return data.results.length;
} catch (error) {
console.error("Parsing error", e);
console.error("Text we were parsing:", text);
}
})
.catch((e) => {
console.error("Catch 2", e);
});
// ...do something with `n`...
} catch (e) {
console.error("Catch 3", e);
}
}
But a couple of things there:
I wouldn't mix async/await with explicit promise callbacks like that.
With that and with your original code, errors will result in n receive the value undefined, because the catch handlers (and my new try/catch block in the then handler) don't return anything.
Instead:
async function test() {
try {
const res = await fetch("https://stackoverflow.com");
if (!res.ok) {
throw new Error(`HTTP error ${res.status}`);
}
const text = await res.text();
// *** you can look at `text` here in a debugger, or
// *** log it, save it, etc., before parsing below
// *** (which might throw an error)
try {
const data = JSON.parse(text);
const n = data.results.length;
// ...do something with `n`...
} catch (error) {
console.error("Parsing error", e);
console.error("Text we were parsing:", text);
}
} catch (e) {
console.error("Catch 3", e);
}
}
Or if you want to respond differently to the parsing error, wrap that bit in a try/catch, etc.
You shouldn't confuse the catch which catching errors in the fetch function itself - with the response errors
fetch("/developer.mozilla.org")
.then(res => {
if (!res.ok) {
console.log("there was an error also here") <= this code also runs
console.log("response is", res);
}
return res.json()
})
.then(data => data.results.length)
.catch(e => {
console.error("Catch 2", e);
})
In your case, you tried converting data -> JSON w/o success, it failed and dropped to the "catch" section.
but to inspect the response - you can dump it in the first section above where I added res.ok
I believe you could do something like this when using promise style Javascript:
const fetchDataPromise = () => {
fetch('https://stackoverflow.com').then((res) => {
res.json().then((jsonData) => {
console.log(jsonData)
}).catch((err) => {
console.error(err)
res.text().then((rawData) => {
console.log(rawData)
}).catch((err) => console.error(err))
})
})
}
Also more intuitive approach would be to use async/await (the trade-off is that you will have to do the API call again):
const fetchData = async () => {
try {
const res = await fetch('https://stackoverflow.com')
const jsonData = await res.json()
console.log(jsonData)
} catch (err) {
try {
console.error(err)
const res = await fetch('https://stackoverflow.com')
const rawData = await res.text()
console.log(rawData)
} catch (rawError) {
console.error(rawError)
}
}
}
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
}
})
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);
});
I am building out an Express API built with the mssql package.
If I don't call sql.close() then I get the following error:
Error: Global connection already exists. Call sql.close() first.
I'd like to keep the endpoints easy to follow and maintain and like the following pattern using a finally promise pattern.
const sql = require("mssql")
const config = require("../config")
sql.connect(config.properties).then(pool => {
return pool.request()
.execute('chain')
.then(response => {
res.send(response['recordsets'][0][0]['response'])
})
.catch(err => res.send(err))
.finally(sql.close())
})
However, this generates the following error:
{
"code": "ENOTOPEN",
"name": "ConnectionError"
}
The following code works, but it seems a bit clumsy to define sql.close multiple times in the same function.
sql.connect(config.properties).then(pool => {
return pool.request()
.execute('chain')
.then(response => {
res.send(response['recordsets'][0][0]['response'])
sql.close()
})
.catch(err => {
res.send(err)
sql.close()
})
})
Is there a way to call sql.close as part of the promise chain after either a response or error is sent with res.send?
.finally accepts function, you passing result of function
sql.connect(config.properties).then(pool => {
return pool.request()
.execute('chain')
.then(response => {
res.send(response['recordsets'][0][0]['response'])
})
.catch(err => res.send(err))
.finally(() => sql.close()) // FIX HERE
})
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.