I'm currently trying to return a value from my async function but getting returned a pending Promise. Here is my code below:
let checker = async(url) => {
const result = await axios(url)
.then(({ data }) => {
if (data.status === "pending") {
return true;
} else {
return false;
}
})
.catch((err) => console.log(err));
return result;
}
let n = 1;
while (n < 3) {
let result = checker(url);
if (result === true){
//do some stuff
};
n++;
}
I also tried returning the result in Promise.resolve(result) but still got a pending Promise. Can someone help me fix this?
I can first return a Promise.resolve(result) in my checker function.
Then, I can use the result of the checker by invoking it in my while loop and accessing it after the .then.
while (n < 3) {
checker(url)
.then((result) =>
//use result
)
n++;
}
Related
I have an existing async function:
async doJSONGetRequest(getUrl, accessToken) {
return new Promise(function(resolve, reject) {
const reqHeaders = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
};
console.info('url = ' + getUrl);
request.get({
url: getUrl,
headers: reqHeaders,
}, function(err, response) {
if (err) return reject(err);
try {
// console.debug(`response = ${response.body}`);
const parsed = JSON.parse(response.body);
return resolve(parsed);
} catch (err) {
return reject(err);
}
});
});
}
}
I'm trying to test it with Jasmine(v4).
Of course, I don't want this thing to actually make an HTTP request, so I tried rigging up a spy on the 'request' package's 'get' function in the 'beforeAll' section:
describe('RAPIDAPIService', function() {
beforeAll(async function() {
spyOn(request, 'get')
.and
.callFake(async (parameters) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
return rsp;
} else if (parameters.url === 'http://localhost/api/whoops') {
return new Error('401 not found');
} else {
return null;
}
});
});
it('doJSONGetRequest should run successfully', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/getSomething', '12345678');
expect(data).toEqual('good stuff');
});
it('doJSONGetRequest should resolve errors properly', async () => {
expect(api.doJSONGetRequest).toBeDefined();
const res = await api.doJSONGetRequest('http://localhost/api/whoops', '12345678');
const expectedError = new Error('401 not found');
expect(res).toEqual(expectedError);
});
Console log statements seem to indicate that I'm actually getting past / returning something from my "await" calls in the "it" tests. But the spies are actually working / detecting that the url's have been called.
(Note that I'm not including here other tests in the same file that do not make asynchronous calls and ARE working... just so you know that there's no problem accessing the actual "api" library and its functions.)
These two tests keep failing with "Error: Timeout - Async function did not complete within 5000ms". And like I said, it seems like they're not returning back to the tests from their calls to the doJSONGetRequest function.
Any thoughts?
Thanks!
I am thinking the issue is the mocking. request.get seems to take two parameters and I am thinking you need to call the 2nd parameter (callback function) once you are done so the resolve can be called.
Try this:
spyOn(request, 'get')
.and
// add callbackFunction as 2nd argument
.callFake((parameters, callbackFunction) => {
if (parameters.url === 'http://localhost/api/getSomething') {
const rsp = {};
rsp.body = 'good stuff';
callbackFunction(null, rsp);
} else if (parameters.url === 'http://localhost/api/whoops') {
callbackFunction({ error: '401 not found' }, {});
} else {
callbackFunction(null, null);
}
});
How to increment my klik data?
klik = async() => {
var ref = await firebase.database().ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik')
ref.transaction(function(klik) {
return klik + 1
})
}
Here is an image of my Firebase Realtime Database:
On this line, you create a reference to a location in your database:
var ref = await firebase.database().ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik')
As this line isn't asynchronous, await isn't needed here and you should use const/let instead of var where possible in modern JavaScript.
const ref = firebase.database()
.ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik')
On the next line:
ref.transaction(function(klik) {
return klik + 1
})
You start a transaction (docs) but don't listen for its result (which is a Promise<{ committed: boolean, snapshot: DataSnapshot | null }>).
// ⬇⬇ return added here to let the caller of `klik()` handle errors
return ref.transaction(function (klik) {
// klik may be null! (parent document doesn't exist?)
// even though `null + 1 == 1`, you might want to do something
return klik + 1;
})
Which ends up with:
klik = async () => {
const ref = firebase.database()
.ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik');
return ref.transaction(function(klik) {
return klik + 1
});
}
or the simpler:
klik = async () => {
return firebase.database()
.ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik')
.transaction(klik => klik + 1);
}
Because you are only incrementing a value, you can cut out the transaction by using a ServerValue.increment() instruction (docs) instead to make the change on the server itself:
klik = async () => {
return firebase.database()
.ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik')
.set(firebase.database.ServerValue.increment(1));
}
If klik is the event handler for a button, you should make sure to handle the success or failure of the database write in the handler for klik:
const addKlik = async () => {
return firebase.database()
.ref('/listPenerbangan/001/-MeFcuKWWE62PCvhJysB/klik')
.set(firebase.database.ServerValue.increment(1));
}
klik = () => {
return addKlik()
.then(() => {
// klik added, do something?
// (or remove this `.then()`)
})
.catch((err) => {
// TODO: handle errors better than this
console.error("klik failed: ", err);
alert("klik failed! " + (err.code || err.message));
});
}
How do I retry this fetch x times if it fails?
The code is based on this article: https://dmitripavlutin.com/javascript-fetch-async-await/
async function fetchData() {
const [firstResponse, secondResponse] = await Promise.all([
fetch(firstUrl),
fetch(secondUrl),
]);
const first = await firstResponse.json();
const second = await secondResponse.json();
return [first, second];
}
fetchData()
.then(([first, second]) => {
console.log("success");
})
.catch((error) => {
console.log("error");
});
Since the requests are independent of each other, I'd have a utility function that will retry X times and then use that in the Promise.all. I'd also have a utility function for fetching JSON that handles the fetch API footgun where it doesn't check HTTP success (see my blog post here). So something along these lines:
// Fetch JSON
function fetchJSON(...args) {
const response = await fetch(...args);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
return response.json();
}
// Fetch JSON with up to `retries` retries
async fetchJSONWithRetry(retries, ...args) {
while (retries > 0) {
try {
const result = await fetchJSON(...args);
return result;
} catch (e) {
if (--retries === 0) {
throw e;
}
}
}
}
// Your `fetchData`
async function fetchData(retries = 5) {
const [first, second] = await Promise.all([
fetchJSONWithRetry(retries, firstUrl),
fetchJSONWithRetry(retries, secondUrl),
]);
return [first, second];
}
I am trying to return a promise from dispatch so that I can do something like this in my react component
this.props.dispatch(requestLogin(data))
.then((res) => {
Navigate.toHome()
}).catch((err) => {
this.showErrorMessage()
})
currently I wrapped my fetch to reuse the common things i pass on the server API and to put some logs for debugging. I did it like this:
export const query = (path, opts) => {
// common config and boilerplates here
// e.g add device id to every api request
return fetch(opts.url, reqOpts)
.then((response) => {
console.log('response received')
if (response.ok) {
return response.json()
} else
console.log('response not ok')})
.then((respData) => {
if (respData.status === true) {
console.log('response success')
return respData
} else {
const errObj = respData
errObj.server = true
throw errObj
}
}).catch((err) => {
console.log('error catched')
if (err.server) {
throw err
}
throw { status: false, errors: { error_code: 'ERR_FATAL', error_msg: 'Something went wrong.' }, err }
})
then my action creator is like this:
export function requestLogin (data) {
return function (dispatch) {
const opts = {
method: 'POST',
body: data,
}
return query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return data2
},
).catch((err) => {
// this is not executed
return err
})
}
}
whats happening is
this.props.dispatch(requestLogin(data))
.then((res) => {
// the error actually goes here
Navigate.toHome()
}
(err) => {
// not here
}).catch((err) => {
// or here
this.showErrorMessage()
})
First, it's important to understand that the second argument you give then(onFulfilled, onRejected), which is onRejected, is another syntax to catch, so because it's written before your catch in the action creator, you get to there when the query function throws an error. that is why the catch block isn't executed. (read about promise's then).
after you catch your error in onRejected, it returns a promise, which is not an error anymore(the promise's state is fulfilled and not rejected).
if you want the promise to get to the catch block, you should change your action creator:
return query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return new Promise((resolve,reject) => {
reject(data2)
}
})
that will return a promise, which is rejected, so it will be caught by the catch block.
also, you can change the
return new Promise((resolve,reject) => {
reject(data2)
}
with
throw 'error'
or
Promise.reject(data2)
let me know if you need any further explanation.
When you doing:
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
},
(data2) => {
// the thrown error actually returns here
// this returned value goes to the .then of the dispatch
return data2
})
.catch((err) => {
// this is not executed
return err
})
It's actually, you do catch the error of query function already, then you return data2. It means you want to return a Promise success (resolve) with data2. The same thing happen with catch.
To fix it, you just need to remove the (data2) => {} and the catch block.
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
})
The second way, in case you still want to do something with the error before, you need to return Promise.reject:
query(Paths.OP_USR_LOGIN, opts)
.then((data) => {
data.TYPE = APP_LOGIN
dispatch(resultData)
})
.catch((err) => {
// you can do something with error, and still return a promise.reject here
console.log('I found an error here', err)
return Promise.reject(err)
})
I am trying to call a function located in service class,and if that function returns data,one boolean variable sets true. I have 2 class as bollow:student.ts and service.ts:
// student.ts
public ngOnInit() {
this.config.load().then(() => {
this.service.getRecords().then(
function () { console.log("success getRecord");
this.loading = false; },
function () { console.log("failed getRecord");
this.loading = true; });
});
}
//service.ts
public getRecord(id: number): Promise<T> {
return this.getRecordImpl();
}
private getRecordsImpl(): Promise<T[]> {
let url = this.serviceUrl;
return this.http.get(url, this.getRequestOptionsWithToken())
.toPromise()
.then(res => {
this.records = this.extractData<T[]>(res);
for (var i = 0; i < this.records.length; i++) {
var record = this.records[i];
this.onRecord(record);
}
return this.records;
})
.catch(this.handleError);
}
by the now, records from service returns, but this.service.getRecords(); is undefined. and I can't use
.then
for handling succeed and failure actions.
I know that it is not good idea to make it synchronous. but think that being Asynchronous causes getRecords becomes undefined. What is the solution for handling that. I want it runs sequentially. and if service returns any records , variable initialize to false, otherwise it sets to true.
Many thanks for any help and guide.
I think your aproach is not correct, what is the point to make a promise synchronous ? If you really really want to do this I suggest you to dig in the Synchronous programming with es6 generators but usually the job is done much smother.
From your code I see that you are consuming your Promise by attaching .then() in the service. In this way you should create a new Promise.
private getRecordsImpl(): Promise<T[]> {
let url = this.serviceUrl;
return new Promise((resolve, reject) => {
this.http.get(url, this.getRequestOptionsWithToken())
.toPromise()
.then(res => {
this.records = this.extractData<T[]>(res);
for (var i = 0; i < this.records.length; i++) {
var record = this.records[i];
this.onRecord(record);
}
resolve(this.records);
})
.catch(this.handleError);
})
}
And in your code use:
this.service.getRecords().then(
function (records) { console.log("success getRecord");
this.loading = false; },
function (err) { console.log("failed getRecord");
this.loading = true; });