I am using expressjs to retrieve data from elasticsearch and send back to my angular app at the front end. Currently I am facing a problem since expressjs doesn't wait until the query execution is finished. I searched for a solution for that and the community says use "Promise or Sync". But I cant figure out where should I use it. I tried to use it but I am getting errors.
This is where I am receiving the request from the frontend and calling the elasticsearch query for send the response.
api.post('/clsDependencies', (req, res) => {
classDependencies(req.body.className);
res.json(messages);
});
This the function for querying the elasticsearch.
function classDependencies(csName) {
let body = {
size: 20,
from: 0,
query: {
match: {
ClassName: {
query: csName
}
}
}
};
search('testclass', body)
.then(results => {
results.hits.hits.forEach((hit, index) => hit._source.dependencies.forEach(
function(myClass){
messages.push({text: myClass.methodSignature , owner: `\t${++nmb} -
${myClass.dependedntClass}`});
}))})
.catch(console.error);
};
Expected data gets initialized to the variable(messages) which I am trying to send back to the front end. But the variable doesn't get initialized at the time when response is send back. What Should I do to wait till the query execution finish before send back the data to frontend.
EDIT
messages is defined outside of both functions.
function classDirectory(className) {
let body = {
size: 20,
from: 0,
query: {
match: {
ClassName: {
query: className
}
}
}
};
return search('testclass', body).then(results => {
results.hits.hits.forEach((hit, index) =>
getDirectories(hit._source.JarFileName));
return messages;
})
.catch(function(err) {
// log the error, but keep the promise rejected
console.error(err);
throw err;
});
};
function getDirectories(jarName) {
let body = {
size: 20,
from: 0,
query: {
match: {
jarFileName: {
query: jarName
}
}
}
};
return search('testjar', body).then(results => {
results.hits.hits.forEach((hit, index) =>
messages.push({text: hit._source.jarFileName , owner: `\t${++nmb} -
${hit._source.directory}`})
);
return messages;
})
.catch(function(err) {
// log the error, but keep the promise rejected
console.error(err);
throw err;
});
};
The Javascript interpreter does not "block" when you make asynchronous calls. This has absolutely nothing to do with Express.
Your call to search() is non-blocking so while it's in process, classDependencies() returns and the rest of your code continues to run. This is the way asynchronous calls in Javascript work.
If you want to call res.json() when classDependencies() is done, then return a promise from it and call res.json() when that promise resolves.
You could do something like this:
api.post('/clsDependencies', (req, res) => {
classDependencies(req.body.className).then(messages => {
res.json(messages);
}).catch(err => {
res.status(500).send(something here);
});
});
function classDependencies(csName) {
let body = {
size: 20,
from: 0,
query: {
match: {
ClassName: {
query: csName
}
}
}
};
return search('testclass', body).then(results => {
let messages = [];
results.hits.hits.forEach((hit, index) => hit._source.dependencies.forEach(function(myClass) {
messages.push({
text: myClass.methodSignature,
owner: `\t${++nmb} - ${myClass.dependedntClass}`
});
}));
// make messages be the resolved value of the returns promise
return messages;
}).catch(function(err) {
// log the error, but keep the promise rejected
console.error(err);
throw err;
});
};
api.post('/clsDirectory', (req, res) => {
classDependency(req.body.className, res);
});
function classDependency(csName, cb) {
let body = {
size: 20,
from: 0,
query: {
match: {
ClassName: {
query: csName
}
}
}
};
search('testclass', body)
.then(results => {
results.hits.hits.forEach((hit, index) =>
hit._source.dependencies.forEach(
function(myClass){
messages.push({text: myClass.methodSignature , owner: `\t${++nmb} -
${myClass.dependedntClass}`});
}));
cb.json(messages);
})
.catch(console.error);
};
Related
I have a function that calls a method that is in my Helper.js file.
import { getTest } from '../../common/Helper';
...
myMethod() {
...
const test = getTest(this.state.myID);
console.log(test);
}
...
My Helper.js:
export const getTest = (pID) => {
axios.get('http://myserver.com/', {
params: {
method: 'getVacantUnits',
propertyID: pID
}
}).then((response) => {
console.log(response.data);
return response.data;
}).catch((error) => {
// handle error
console.log(error);
return 0;
});
};
It is odd because my output is:
undefined
myDataContent
It looks like that "const test" is receiving undefined before the getTest being run. Why is it happening?
Thanks
It's returning this first since it's not awaiting the result:
console.log(test);
2 easy ways to fix this I am showing below, first with promise:
const test = getTest(this.state.myID).then(response=> console.log(response)).catch(err => console.log(err))
Add in return as well since you need to return from outermost function
export const getTest = (pID) => {
return axios.get('http://myserver.com/', {
params: {
method: 'getVacantUnits',
propertyID: pID
}
}).then((response) => {
console.log(response.data);
return response.data;
}).catch((error) => {
// handle error
console.log(error);
return 0;
});
};
second using async await:
// add in await
export const getTest = async (pID) => {
return axios.get('http://myserver.com/', {
params: {
method: 'getVacantUnits',
propertyID: pID
}
}).then((response) => {
console.log(response.data);
return response.data;
}).catch((error) => {
// handle error
console.log(error);
return 0;
});
};
// here you are awaiting the response before you run console.log
const test = await getTest(this.state.myID);
console.log(test);
You can solve this in several other ways, but I think these are the 2 easiest. Basically think about the fact that those are run synchronously and the console.log executes before the function returns, so if you "wait" then it makes it so the console.log() is dependent on the first function executing first.
I have this function that I want to wait of the result of and then use it:
getUserId = () => {
fetch("https://www.dummysite.com/mobile/person-id", {
credentials: "include",
method: "GET",
headers: {
Cookie: this.state.auth_token_res
}
}).then(res => {
let id_obj = JSON.parse(res._bodyText);
console.log("parsed json", id_obj);
return id_obj.data;
});
};
I want to use it in this function:
async sendID() {
let user_id = await this.getUserId();
console.log(user_id);
OneSignal.sendTags({
user_id: user_id
})
.then(function(tagsSent) {
// Callback called when tags have finished sending
console.log("tag is set: ", tagsSent);
})
.catch(err => {
console.log("error", err);
});
}
I don't see any syntax problems, and the app compiles, but when it starts it just hows this error:
error image
the other weird this is if i turn on remote debugging on this screen I get a different error:
error 2
here is says that await is not in an async function but it is, and I am not getting syntax error in my editor or in the metro bundler.
There a few things you might have missed. Consider these changes. Although I didn't get a chance to test it, I am confident it will work or at least put you on a right track.
getUserId = () => {
// return fetch in order to await
return fetch("https://www.dummysite.com/mobile/person-id", {
credentials: "include",
method: "GET",
headers: {
Cookie: this.state.auth_token_res
}
}).then(res => res.json());
};
// make this an arrow function
sendID = async () => {
try {
let user_id = await this.getUserId();
// after printing then decide what to do here;
console.log(user_id);
const tagsSent = await OneSignal.sendTags({
user_id: user_id
});
console.log(tagsSent);
} catch (err) {
console.log(err);
}
}
I'm using the fbsdk to get user details in an ajax request. So it makes sense to do this in a redux-observable epic. The way the fbsdk request goes, it doesn't have a .map() and .catch() it takes the success and failure callbacks:
code:
export const fetchUserDetailsEpic: Epic<*, *, *> = (
action$: ActionsObservable<*>,
store
): Observable<CategoryAction> =>
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
getDetails(store)
})
const getDetails = store => {
console.log(store)
let req = new GraphRequest(
'/me',
{
httpMethod: 'GET',
version: 'v2.5',
parameters: {
fields: {
string: 'email,first_name,last_name'
}
}
},
(err, res) => {
if (err) {
store.dispatch(fetchUserDetailsRejected(err))
} else {
store.dispatch(fetchUserDetailsFulfilled(res))
}
}
)
return new GraphRequestManager().addRequest(req).start()
}
It gives the error:
TypeError: You provided 'undefined' where a stream was expected. You
can provide an Observable, Promise, Array, or Iterable.
How do I return an observable from the epic so this error goes away?
Attempt at bindCallback from this SO answer:
const getDetails = (callBack, details) => {
let req = new GraphRequest(
'/me',
{
httpMethod: 'GET',
version: 'v2.5',
parameters: {
fields: {
string: 'email,first_name,last_name'
}
}
},
callBack(details)
)
new GraphRequestManager().addRequest(req).start()
}
const someFunction = (options, cb) => {
if (typeof options === 'function') {
cb = options
options = null
}
getDetails(cb, null)
}
const getDetailsObservable = Observable.bindCallback(someFunction)
export const fetchUserDetailsEpic: Epic<*, *, *> = (
action$: ActionsObservable<*>
): Observable<CategoryAction> =>
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
getDetailsObservable()
.mergeMap(details => {
return Observable.of(fetchUserDetailsFulfilled(details))
})
.catch(error => Observable.of(fetchUserDetailsRejected(error)))
})
Getting the same error
Looking into source code of GraphRequestManager .start:
start(timeout: ?number) {
const that = this;
const callback = (error, result, response) => {
if (response) {
that.requestCallbacks.forEach((innerCallback, index, array) => {
if (innerCallback) {
innerCallback(response[index][0], response[index][1]);
}
});
}
if (that.batchCallback) {
that.batchCallback(error, result);
}
};
NativeGraphRequestManager.start(this.requestBatch, timeout || 0, callback);
}
As you can see it does return nothing, so effectively undefined. Rx mergeMap requires an instance of Observable or something compatible with it (more info).
Since you dispatch further actions, you can modify your original code like that:
export const fetchUserDetailsEpic: Epic<*, *, *> = (
action$: ActionsObservable<*>,
store
): Observable<CategoryAction> =>
action$.ofType(FETCH_USER_DETAILS).do(() => { // .mergeMap changed to .do
getDetails(store)
})
const getDetails = store => {
console.log(store)
let req = new GraphRequest(
'/me',
{
httpMethod: 'GET',
version: 'v2.5',
parameters: {
fields: {
string: 'email,first_name,last_name'
}
}
},
(err, res) => {
if (err) {
store.dispatch(fetchUserDetailsRejected(err))
} else {
store.dispatch(fetchUserDetailsFulfilled(res))
}
}
)
return new GraphRequestManager().addRequest(req).start()
}
To be honest I find your second attempt bit better / less coupled. To make it working you could do something like:
const getDetails = Observable.create((observer) => {
let req = new GraphRequest(
'/me',
{
httpMethod: 'GET',
version: 'v2.5',
parameters: {
fields: {
string: 'email,first_name,last_name'
}
}
},
(error, details) => {
if (error) {
observer.error(error)
} else {
observer.next(details)
observer.complete()
}
}
)
new GraphRequestManager().addRequest(req).start()
})
export const fetchUserDetailsEpic: Epic<*, *, *> = (
action$: ActionsObservable<*>
): Observable<CategoryAction> =>
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
getDetails()
.map(details => fetchUserDetailsFulfilled(details)) // regular .map should be enough here
.catch(error => Observable.of(fetchUserDetailsRejected(error)))
})
I don't remember well how was working redux-observable before using RxJS >= 6 but I'll try to help ;)
First, you don't need to dispatch yourself, redux-observable will do it for you. In this article, they show how it works under the hood, so they call dispatch, but you don't have to. In the new implementation, they removed store as a second argument in favor of a state stream:
const epic = (action$, store) => { ... //before
const epic = (action$, state$) => { ... //after
But most importantly, the problem you experience is that you don't return a stream of actions, but a single (dispatched) action.
From their website:
It is a function which takes a stream of actions and returns a stream of actions.
So I think a quick solution would be to return observables from your callback:
(err, res) => {
if (err) {
return Observable.of(fetchUserDetailsRejected(err))
}
return Observable.of(fetchUserDetailsFulfilled(res))
}
I will update the answer based on your comments. Good luck!
I beleive this seems the possible reason for undefined. You are returning undefined in mergeMap callback.
This
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
getDetails(store)
})
should be either
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => getDetails(store))
or
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => {
return getDetails(store)
})
It looks like #artur grzesiak has a correct answer, but for completeness this is how I think bindCallback can be used.
The only issue I have with Artur's answer is I don't think we need to catch the error in the epic, since fetchUserDetailsRejected is an error-handling action (presumably the reducer deals with it appropriately).
I used this reference RxJs Static Public Methods: bindCallback
Give it a function f of type f(x, callback) and it will return a function g that when called as g(x) will output an Observable.
// This callback receives the results and returns one or other action (non-observable)
const callback = (err, res) => {
return err
? fetchUserDetailsRejected(err)
: fetchUserDetailsFulfilled(res)
}
// Here is the graph request uncluttered by concerns about the epic
const getDetails = (store, callback) => {
console.log(store)
let req = new GraphRequest(
'/me',
{
httpMethod: 'GET',
version: 'v2.5',
parameters: {
fields: {
string: 'email,first_name,last_name'
}
}
},
callback
)
new GraphRequestManager().addRequest(req).start()
}
// This bound function wraps the action returned from callback in an Observable
const getDetails$ = Observable.bindCallback(getDetails).take(1)
// The epic definition using bound callback to return an Observable action
export const fetchUserDetailsEpic: Epic<*, *, *> =
(action$: ActionsObservable<*>, store): Observable<CategoryAction> =>
action$.ofType(FETCH_USER_DETAILS).mergeMap(() => getDetails$(store))
I have a user login function that is working. But, I want to incorporate a time out error for the fetch. Is there a way to set up a timer for 5 seconds or so that would stop trying to fetch after such a time? Otherwise, I just get a red screen after a while saying network error.
_userLogin() {
var value = this.refs.form.getValue();
if (value) {
// if validation fails, value will be null
if (!this.validateEmail(value.email)) {
// eslint-disable-next-line no-undef
Alert.alert('Enter a valid email');
} else {
fetch('http://51.64.34.134:5000/api/login', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
timeout: 5000,
body: JSON.stringify({
username: value.email,
password: value.password,
}),
})
.then((response) => response.json())
.then((responseData) => {
if (responseData.status == 'success') {
this._onValueChange(STORAGE_KEY, responseData.data.token);
Alert.alert('Login Success!');
this.props.navigator.push({name: 'StartScreen'});
} else if (responseData.status == 'error') {
Alert.alert('Login Error', responseData.message);
}
})
.done();
}
}
}
I have made a ES6 function that wraps ES fetch into a promise, here it is:
export async function fetchWithTimeout(url, options, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
]);
}
Here is how to use it:
const requestInfo = {
method,
headers,
body,
};
const url = 'http://yoururl.edu.br'
let data = await fetchWithTimeout(url, requestInfo, 3000);
// Wrapper function for fetch
const fetchSomething = async () => {
let controller = new AbortController()
setTimeout(() => controller.abort(), 3000); // abort after 3 seconds
const resp = await fetch('some url', {signal: controller.signal});
const json = await resp.json();
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
return json;
}
// usage
try {
let jsonResp = await fetchSomthing();
console.log(jsonResp);
} catch (error) {
if (error.name === 'AbortError') {
console.log('Network Error');
} else {
console.log(error.message);
}
}
I think using AbortController is the recommended way to abort a fetch call. The code snippet above handles the following scenarios:
If network is good but HTTP returns an error status, the message "HTTP error! ..." will be logged.
If network is down, setTimeout would trigger the AbortController to abort fetch after three seconds. The message "Network Error" will be logged.
If network is good and HTTP response is good, the response JSON will be logged.
The documentation for using AbortController to abort fetch is here.
There is no standard way of handling this as a timeout option isn't defined in the official spec yet. There is an abort defined which you can use in conjunction with your own timeout and Promises. For example as seen here and here. I've copied the example code, but haven't tested it myself yet.
// Rough implementation. Untested.
function timeout(ms, promise) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("timeout"))
}, ms)
promise.then(resolve, reject)
})
}
timeout(1000, fetch('/hello')).then(function(response) {
// process response
}).catch(function(error) {
// might be a timeout error
})
Another option would be to modify the fetch.js module yourself to add a timeout that calls abort as seen here.
This is what I did to go around it:
(This is the "generic" function I use to make all calls on my app)
I created a timeout function, that will be triggered unless it is cleared before, then I clear this timeout on server response
const doFetch = (url, callback, data) => {
//... creating config obj here (not relevant for this answer)
var wasServerTimeout = false;
var timeout = setTimeout(() => {
wasServerTimeout = true;
alert('Time Out');
}, 3000);
fetch(HOST + url, config)
.then((response) => {
timeout && clearTimeout(timeout); //If everything is ok, clear the timeout
if (!wasServerTimeout) {
return response.json();
}
})
.then((response) => {
callback && callback(response.data || response);
})
.catch((err) => {
//If something goes wrong, clear the timeout
timeout && clearTimeout(timeout);
if (!wasServerTimeout) {
//Error logic here
}
});
};
I solved this problem by using a race between 2 promises, written as a wrapper around fetch. In my case I expect the request to return json so also added that. Maybe there is a better solution, but this works correctly for me!
The wrapper returns a promise which will resolve as long as there are no code errors.
You can check the result.status for 'success' and read json data from result.data. In case of error you can read the exact error in result.data, and display it or log it somewhere. This way you always know what went wrong!
var yourFetchWrapperFunction = function (
method,
url,
headers,
body,
timeout = 5000,
) {
var timeoutPromise = new Promise(function (resolve, reject) {
setTimeout(resolve, timeout, {
status: 'error',
code: 666,
data:
'Verbinding met de cloud kon niet tot stand gebracht worden: Timeout.',
});
});
return Promise.race([
timeoutPromise,
fetch(connectionType + '://' + url, {
method: method,
headers: headers,
body: body,
}),
])
.then(
(result) => {
var Status = result.status;
return result
.json()
.then(
function (data) {
if (Status === 200 || Status === 0) {
return {status: 'success', code: Status, data: data};
} else {
return {
status: 'error',
code: Status,
data: 'Error (' + data.status_code + '): ' + data.message,
};
}
},
function (response) {
return {
status: 'error',
code: Status,
data: 'json promise failed' + response,
};
},
)
.catch((error) => {
return {status: 'error', code: 666, data: 'no json response'};
});
},
function (error) {
return {status: 'error', code: 666, data: 'connection timed out'};
},
)
.catch((error) => {
return {status: 'error', code: 666, data: 'connection timed out'};
});
};
let controller = new AbortController()
setTimeout( () => {
controller.abort()
}, 10000); // 10,000 means 10 seconds
return fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(param),
signal: controller.signal
})
I may be late but i made a code which is 100% working to timeout an API request using fetch.
fetch_timeout(url, options) {
let timeout = 1000;
let timeout_err = {
ok: false,
status: 408,
};
return new Promise(function (resolve, reject) {
fetch(url, options)
.then(resolve, reject)
.catch(() => {
alert('timeout.');
});
setTimeout(reject.bind(null, timeout_err), timeout);
});
}
You just need to pass the api-endpoint to the url and body to the options parameter.
Does anyone have an example on how to use promise with GraphRequestManager?
I get Cannot read property then of undefined error in my action creator.
function graphRequest(path, params, token=undefined, version=undefined, method='GET') {
return new Promise((resolve, reject) => {
new GraphRequestManager().addRequest(new GraphRequest(
path,
{
httpMethod: method,
version: version,
accessToken: token
},
(error, result) => {
if (error) {
console.log('Error fetching data: ' + error);
reject('error making request. ' + error);
} else {
console.log('Success fetching data: ');
console.log(result);
resolve(result);
}
},
)).start();
});
}
I call the above using my action creator
export function accounts() {
return dispatch => {
console.log("fetching accounts!!!!!!");
dispatch(accountsFetch());
fbAPI.accounts().then((accounts) => {
dispatch(accountsFetchSuccess(accounts));
}).catch((error) => {
dispatch(accountsFetchFailure(error));
})
}
}
I get 'Success fetching data:' in the console along with the result before the error. So the API call is made successfully. The error is after fetching the accounts in fbAPI.accounts().then((accounts) which I think is due to GraphRequestManager returning immediately instead of waiting.
I have a solution for you.
My provider look like this :
FBGraphRequest = async (fields) => {
const accessData = await AccessToken.getCurrentAccessToken();
// Create a graph request asking for user information
return new Promise((resolve, reject) => {
const infoRequest = new GraphRequest('/me', {
accessToken: accessData.accessToken,
parameters: {
fields: {
string: fields
}
}
},
(error, result) => {
if (error) {
console.log('Error fetching data: ' + error.toString());
reject(error);
} else {
resolve(result);
}
});
new GraphRequestManager().addRequest(infoRequest).start();
});
};
triggerGraphRequest = async () => {
let result = await this.FBGraphRequest('id, email');
return result;
}
That works great ! I let you adapt my solution to your system.