I'm having a little trouble dealing with some Promises in my app, any clarification would be much appreciated.
I've been building a Phoenix/React app loosely based on this tutorial - https://medium.com/#benhansen/lets-build-a-slack-clone-with-elixir-phoenix-and-react-part-3-frontend-authentication-373e0a713e9e - and I'm trying to restructure my code a bit to make it easier for me to build out other aspects of the app in the future.
Initially, when posting login data to my Phoenix server, the function that I was using looked like this (from Login.jsx):
fetch(`${apiUrl}/sessions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({person: person})
}).then(response => {
this.setState({loadingData: false}, () => {
response.json().then(result => {
if(result.status === "error"){
this.setState({error: {isError: true, message: result.message}}, () => {
return;
})
}
else{
this.login(result) //DO SOME OTHER STUFF WITH THE RESULT
}
})
})
}).catch(error => {
console.error("There was an error: " + error);
});
and this worked just fine.
However, I have since restructured my code so that the fetch functionality has been moved into another file. Here's how it looks now (somewhat similar to the tutorial):
fetch.js
let parseResponse = (response) => {
return response.json().then((json) => {
if (!response.ok){
return Promise.reject(json)
}
return json;
});
}
let fetchFunctions = {
post: (url, data) => {
const body = JSON.stringify(data)
fetch(`${apiUrl}${url}`, {
method: 'POST',
headers: headers(),
body: body
})
.then(parseResponse)
}
}
export default fetchFunctions;
Login.jsx
post('/sessions', {person: person})
.then((result) => {
this.login(result) //HERE'S THAT LOGIN FUNCTION I WANT TO RUN
})
Now when I run this, you may not be surprised to learn that I get the error Uncaught TypeError: Cannot read property 'then' of undefined, and I get it, I think... please correct me if I'm wrong, but the reason that this doesn't work is because fetch() is a Promise, but I have now wrapped it inside of a function that is not a Promise.
If I add console.log(json) before the return statement in parseResponse(), I do see my data and it looks good... but how can I get that data out of the Promise and into my component? It seems to me that I need to defined post() as a Promise as well, but I'm not sure how to structure this.
but the reason that this doesn't work is because fetch() is a Promise, but I have now wrapped it inside of a function that is not a Promise.
Functions are not promises. Functions can return promises. You simply forgot to return the result of fetch, which is a promise, from post:
let fetchFunctions = {
post: (url, data) => {
const body = JSON.stringify(data)
return fetch(`${apiUrl}${url}`, {
// ^^^^^^
method: 'POST',
headers: headers(),
body: body
})
.then(parseResponse)
}
}
Now post returns a promises as well.
If you don't return, the implicit return value will be undefined, hence the error message "Uncaught TypeError: Cannot read property 'then' of undefined"
Simplest repro case for this error:
function foo(){}
foo().then();
Related
First of all, I'm aware this is not a good approach, need it as temporary solution for certain functions to return value, not promise. I know it's really not good permanent solution at all, but I need it for now.
What worries me, fetch sure finishes sooner - but it runs until the whiles times out, and then to console comes first the RETVAL false, and only then second line comes RETFETCH: {....} with returned json values - it seems the 'haveResponse' value does not change in the second 'then' - but can't see why, and how to bypass it.
It's a temporary workaround for old sync fns to read some data from remote service running on local pc on some port, but for now I can't rewrite the function which expects to receive data from this fn, so there must be no promise on the outside, need to wait for response and then return it.
function syncFetch(url) {
var haveResponse = false;
var reqtime = new Date();
try{
fetch(url, {
headers: {'Content-Type': 'application/json'},
method: 'POST',
timeout: 1500,
body: JSON.stringify({cmd:'init'})
})
.then(response => response.json())
.then(data => {
console.log('RETFETCH:', data);
haveResponse = data;
return data;
});
// timeout
while (haveResponse === false) {
var endDate = new Date();
if (haveResponse !== false) { return haveResponse; }
if ((endDate - reqtime)/1000 > 5) { // max 5 sec
return haveResponse;
}
}
return haveResponse;
} catch(e){
console.log('error', e);
haveResponse = -1;
}
return haveResponse;
}
console.log('RETVAL',syncFetch('http://127.0.0.1:3333/'));
Save yourself a few headaches, drop all the .then() and use the async/await syntax instead. No need for dirty timeout/while hacks.
I renamed syncFetch to asyncFetch, because your original code was never synchronous in the first place (and that's precisely why you are struggling so much, you believe it is when it's not). async/await don't make the code synchronous either. It's just awesome syntactic sugar around Promises.
(EDIT : you said in the comments that you can't add the async keyword to the parent function (asyncFetch), so here's a workaround to place it inside :
function asyncFetch(url) {
async function a() {
try {
const response = fetch(url, {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
timeout: 1500,
body: JSON.stringify({ cmd: 'init' })
});
const data = await response.json();
return data; // This is a Promise<data>, not data directly, so it needs to be awaited
} catch (e) {
console.log('error', e);
return null
}
};
return a();
};
(async () => {
console.log('RETVAL', await asyncFetch('http://127.0.0.1:3333/')); // Needs to be awaited, and therefore also needs to be inside an async function
})();
I have this piece of code that calls a function getTableData and expects a Promise in return.
function populateTableRows(url) {
successCallback = () => { ... };
errorCallback = () => { ... };
getTableData(url, successCallback, errorCallback).then(tableData => {
// do stuff with tableData
}
}
This is used in many places across my codebase, and I'm looking to keep the behavior the same as I move away from using jQuery's ajax (and jQuery in general)
In getTableData, I'm currently using $.ajax like so
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
const ajaxOptions = {
type: 'POST',
url: url,
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: { // some data }
};
return $.ajax(ajaxOptions).done(successCallback).fail(errorCallback);
}
This currently returns a Promise for successful requests. For bad requests where fail is invoked, it doesn't appear that a Promise is returned and the then doesn't run in the calling function (which is okay in this case).
When converting the request over to use fetch, I have something like this
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: { // some data }
})
.then(response => {
let json = response.json();
if (response.status >= 200 && response.status < 300) {
successCallback(json);
return json;
} else {
return json.then(error => {throw error;});
}
}).catch((error) => {
errorCallback(error);
return
});
Successful requests appear to be behaving similarly to the ajax code that I currently have, but now the then callback is running for bad requests which is causing errors in my code.
Is there a way with fetch to mimic the fail behavior of jQuery where the Promise is seemingly aborted for bad requests? I'm fairly new to using Promises and after some experimentation/searching I haven't been able to come up with a solution.
When you .catch() in a chain of promises, it means you already handled the error, and subsequent .then() calls continue successfully.
For example:
apiCall()
.catch((error) => {
console.log(error);
return true; // error handled, returning true here means the promise chain can continue
})
.then(() => {
console.log('still executing if the API call fails');
});
What you want, in your case, is when you handle the error with the callback, to continue to throw it so the promise chain is broken. The chain then further needs a new .catch() block to handle the new error.
apiCall()
.catch((error) => {
console.log(error); // "handled", but we're still not done
throw error; // instead of returning true, we throw the error further
// 👆 this can also be written as `return Promise.reject(error);`
})
.then(() => {
console.log('not executing anymore if the API call fails');
})
.catch((error) => {
// handle the same error we have thrown from the previous catch block
return true; // not throwing anymore, so error is handled
})
.then(() => {
console.log('always executing, since we returned true in the last catch block');
});
By the way, what you return from one then/catch block, the following one will get it as a param.
apiCall()
.then((response) => {
/* do something with response */;
return 1;
})
.catch((error) => { return 'a'; })
.then((x) => console.log(x)) // x is 'a' if there's an error in the API call, or `1` otherwise
In your .catch you implicitly return undefined and thus "handle" the error. The result is a new Promise that fulfills to undefined.
.catch((error) => {
errorCallback(error);
return Promise.reject();
});
should be enough to keep the returned Promise rejecting.
Or you assign the intermediate Promise to a var and return that, and not the result to the fail handling:
var reqPromise = fetch(url, {
// ...
})
.then(response => {
// ...
return json.then(error => {throw error;});
});
reqPromise.catch((error) => {
errorCallback(error);
return
});
return reqPromise;
I am using react-native, mongo DB and node js and I need to create some database functions and put them in some modules to be able to reuse them whenever I want. To fetch data from the mongo database, I use the fetch() function which returns a promise. So, for all functions that I created that did not return a value, I used .then and I faced no problems. On the other side, when I return a value inside a fetch().then() function and use this returned value, I get undefined. The code I use for the function looks like:
export const getUsers = () => {
//I cannot use this function because of returning a promise
fetch("http://1jjsd12zaws.ngrok.io/database/", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
res.json();
})
.then((data) => {
return JSON.stringify(data);
});
};
Then, when I try to run this code:
let users=getUsers();
console.log(users);
It prints undefined.
What I think is going on is that the console.log(users) runs before getUsers() returns its value. But I do not know why does this happen and I want it to wait for getUsers() to execute then, completes its work.
You need to return fetch(..) inside getUsers (that's why you are getting undefined)
You also need to return res.json() inside the first then
Since getUsers returns a Promise, then you need to use .then (or async/await) to access the promise value: getUsers().then(users => {...})
const getUsers = () => {
return fetch('http://1jjsd12zaws.ngrok.io/database/', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
.then(res => {
return res.json();
})
.then(data => {
return JSON.stringify(data);
});
};
getUsers().then(users => console.log(users))
Async and await should cover it. The example on MDN docs explains it better than I can and should apply to your use case.
I'm trying to mock console.info which I know will be called when an imported function runs. The function consists entirely of a single fetch which, when not running in production, reports the request and response using console.info.
At the question Jest. How to mock console when it is used by a third-party-library?, the top-rated answer suggests overwriting global.console, so I'm using jest.spyOn to try that out:
import * as ourModule from "../src/ourModule";
test("Thing", () => {
// Tested function requires this. Including it here in case it's causing
// something quirky that readers of this question may know about
global.fetch = require("jest-fetch-mock");
const mockInfo = jest.spyOn(global.console, "info").mockImplementation(
() => { console.error("mockInfo") }
);
ourModule.functionBeingTested("test");
expect(mockInfo).toHaveBeenCalled();
}
As expected, the output contains an instance of "mockInfo". However, then testing that with toHaveBeenCalled() fails.
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called, but it was not called.
40 |
41 | ourModule.functionBeingTested("test");
> 42 | expect(mockInfo).toHaveBeenCalled();
| ^
43 |
at Object.toHaveBeenCalled (__tests__/basic.test.js:42:22)
console.error __tests__/basic.test.js:38
mockInfo
I've tried moving the spyOn to before the module is loaded, as suggested in one of the comments on the answer, with no difference in result. What am I missing here?
Here's the function in question:
function functionBeingTested(value) {
const fetchData = {
something: value
};
fetch("https://example.com/api", {
method: "POST",
mode: "cors",
body: JSON.stringify(fetchData),
})
.then( response => {
if (response.ok) {
if (MODE != "production") {
console.info(fetchData);
console.info(response);
}
} else {
console.error(`${response.status}: ${response.statusText}`);
}
})
.catch( error => {
console.error(error);
});
}
Issue
console.info is called in a Promise callback which hasn't executed by the time ourModule.functionBeingTested returns and the expect runs.
Solution
Make sure the Promise callback that calls console.info has run before running the expect.
The easiest way to do that is to return the Promise from ourModule.functionBeingTested:
function functionBeingTested(value) {
const fetchData = {
something: value
};
return fetch("https://example.com/api", { // return the Promise
method: "POST",
mode: "cors",
body: JSON.stringify(fetchData),
})
.then(response => {
if (response.ok) {
if (MODE != "production") {
console.info(fetchData);
console.info(response);
}
} else {
console.error(`${response.status}: ${response.statusText}`);
}
})
.catch(error => {
console.error(error);
});
}
...and wait for it to resolve before asserting:
test("Thing", async () => { // use an async test function...
// Tested function requires this. Including it here in case it's causing
// something quirky that readers of this question may know about
global.fetch = require("jest-fetch-mock");
const mockInfo = jest.spyOn(global.console, "info").mockImplementation(
() => { console.error("mockInfo") }
);
await ourModule.functionBeingTested("test"); // ...and wait for the Promise to resolve
expect(mockInfo).toHaveBeenCalled(); // SUCCESS
});
I am new to React, Redux and JS overall. I want to know how can I dispatch and action after another is finished - Promises in correct way. My code actually works but it keeps throwing error:
readingActions.js?14b9:56 Uncaught (in promise) TypeError: dispatch(...).then is not a function(…)
This is my setup.
This is my action creator what I want chained action and where warning happends.
export function createReading(reading) {
return function (dispatch) {
dispatch({type: CREATE_READING});
return request(
`${API_URL}new`, {method: 'POST', body:JSON.stringify(reading)},
(json) => {( dispatch({type: CREATE_READING_SUCCESS, res: json}).then(dispatch(Notifications.success(showSuccess(json.book.title)))))},
(json) => { dispatch({type: CREATE_READING_ERROR400, res: json}).then(dispatch(Notifications.error(showError(json.error)))) },
(res) => { dispatch({type: CREATE_READING_ERROR500, res: res}) },
(ex) => { dispatch({type: CREATE_READING_FAILURE, error: ex}) },
)
}
}
As you can see the problem is in .then, since I dont know how to trigger action correctly.
You can also see request that is my helper function that looks like so (here I append token, return different responses):
export function request(url, options, success, error400, error, failure) {
let headers = new Headers();
headers.append("Content-Type", "application/json")
headers.append("Accept", "application/json")
options["headers"] = headers;
if (localStorage.jwtToken) {
let token = localStorage.jwtToken;
headers.append('Authorization', 'JWT '+token);
}
return fetch(url, options)
.then(res => {
if (res.status >= 200 && res.status < 300) {
res.json().then(json => {
return success(json)
})
} else if (res.status === 400) {
res.json().then(json => {
return error400(json)
})
} else {
return error(res)
}
}).catch((ex) => {
return failure(ex)
})
}
Question is how can I execute proper .then and what would be the correct way?
If you want to dispatch actions in chains you can actually implement it on your own.
Now say after analysing a bit you take a pen and paper and start write basic algorithm for how it should work and you come up with following:-
dispatch(action) => action returns function => action returns function => action returns an object(here you chain ends)
Now from above if you see that you create a middleware and keep on dispatching actions which return functions until you get an action with returns an object. This is what redux-thunk does.
So even if you try to create something of your own do that for your learning, but eventually you will come up with something like thunk or maybe some other package.
I would really say give redux-thunk a try.Also for middleware understanding I would recommend you can check redux middleware docs.