How to properly implement async/await - javascript

I am new to using async/await - I am trying to return data from an API call and format/tidy it slightly.
I'm really struggling to work out how to make this work because of the asynchronous nature of the functions. I cannot get the promise to work without the browser simply falling over.
My first function calls the API and gets a response as JSON. I then store a subset of this data json.recommendations
function getRecs() {
const requestUrl = `blahblah`;
const options = {
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
},
method: 'GET',
};
fetch(requestUrl, options).then((res) => {
if (res.ok) {
return res.json();
}
throw new Error('Error!!??', res);
}).then((json) => {
return json.recommendations;
});
}
My second function takes json.recommendations and does some tidying to remove unwanted data and return a new array of data, those that match my filter.
async function getInStockRecs() {
const recs = await getRecs();
if (recs !== undefined) {
return recs.filter(function(rec){
return rec.isInStock === true;
});
}
}
A third function formats the data further:
async function topThreeArray() {
const inStockRecs = await getInStockRecs();
const topThree =[];
for (let i = 0; i < i <= 3; i++) {
topThree.push(inStockRecs[0]);
}
return topThree;
}
By using await I intended each function to only run once the data has been returned properly from the previous. However running the above crashes the page and I cannot do anything to debug as it simply crashes. Where am I going wrong?

You don't return anything in your getRecs() function (you only return in the callbacks to the fetch() call)
Since you're using async-await elsewhere, why not use that for the getRecs() function too?:
async function getRecs() {
const requestUrl = `blahblah`;
const options = {
headers: {
'Content-type': 'application/json',
Accept: 'application/json',
},
method: 'GET',
};
const res = await fetch(requestUrl, options);
if (res.ok) {
return res.json().recommendations;
}
throw new Error('Error!!??', res);
}
Otherwise, you'd have to return the fetch() call itself:
return fetch(requestUrl, options).then((res) => {
...
The reason the browser crashes is because the condition in the for loop in topThreeArray() is weird (i < i <= 3) and results in an infinite loop.
Basically, i < i evaluates to false, which gets implicitly coerced into 0, so the condition effectively becomes 0 <= 3 which is always true.
Finally, I'd like to point out that you should carefully consider if async-await is appropriate in the first place when running in a browser as support for it is still very fragile and scetchy in browsers.

Related

synchronous fetch - unexpected results

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
})();

Function does not wait for fetch request

I am trying to retrieve JSON from a server as an initial state.
But it looks like the function doesn't wait for the response.
I looked at different Stackoverflow posts like How to return return from a promise callback with fetch? but the answers I found didn't seem to help.
// Get the best initial state available
// 1. Check for local storage
// 2. Use hardcoded state with the test value from the server.
export function getInitialState() {
if (localStorage.getItem('Backup') != undefined) {
return returnValue = Map(JSON.parse(localStorage.getItem('Backup')))
} else {
var returnValue = initialState
const GetJSON = (url) => {
let myHeaders = new Headers();
let options = {
method: 'GET',
headers: myHeaders,
mode: 'cors'
};
return fetch(url, options).then(response => response.json());
};
GetJSON('https://example.com/api/v1/endpoint/valid/').then(result => {
// Console.log gives a good result
console.log(result)
// The return is undefined
return returnValue.setIn(['test'], result)
});
}
}
In this case, I receive an undefined while I expect a returnValue JSON where the test property is updated from the server.
So I really want the function getInitialState() to return the state. And to do this it needs to wait for the fetch to finish. I also tried to place return before the GetJSON but this had no effect.
Oke, so I just tried the following:
GetJSON('https://example.com/api/v1/endpoint/valid/').then(result => {
console.log('aaa')
console.log(result)
localstorage.setItem('init', result)
console.log('bbb')
}).then(result => {
console.log('done')
});
This returns aaa and result.
But the localStorage is never set, and the bbb and done are never shown.

async fetch in function

I am trying to create a function, that sends a request to my API with fetch & waits for the result - if it returns data, I want the function to return true, otherwise false.
const loggedIn = async () => {
const response = await fetch(ENDPOINT_URL, {
method: "POST",
credentials: "include",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: query,
})
})
const json = await response.json()
// data gets logged
console.log(json.data)
if(json.data) {
return true
} else {
return false
}
}
If I call loggedIn(), I still receive PromiseĀ {<pending>}?
An async function returns a promise. From a caller's perspective, it's no different from any other function returning a promise. When you call it, you get a promise which you need to handle, not the value, because that would require blocking.
It sounds like you expected it to block your code. To get that, call it from another async function:
(async () => {
try {
console.log(await loggedIn()); // true or false
} catch (e) {
console.log(e);
}
})();

NodeJS - loop with nested API calls

Hello I'm new to NodeJs and am trying to work out the best way to get this chain of events working. I have to do two API calls get all the information I need. The first API call is just a list of IDs, then the second API call I pass the ID to get the rest of the information for each object.
However using the method below, I have no idea when everything is finished. Please can someone help me out.
function getData() {
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 1");
var obj = JSON.parse(apires);
obj.data.forEach(function(entry) {
findMore(entry.id)
});
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 2");
var obj = JSON.parse(apires);
})
}
You can make your findMore method return a promise, so you can pass an array of those to Promise.all and handle the .then when all promises have finished.
function getData() {
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 1");
var obj = JSON.parse(apires);
var promises = [];
obj.data.forEach(function(entry) {
promises.push(findMore(entry.id));
});
return Promise.all(promises);
})
.then(function (response) {
// Here response is an array with all the responses
// from your calls to findMore
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
return request(options);
}
A couple of things to think about:
If you care about the fate of a promise, always return it.
In your case, findMore does not return the promise from request, so getData has no handle to track the resolution (or rejection) of that promise.
You can track the resolution of multiple promises with Promise.all.
The Promise.all() method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.
Lets put these to use on your example:
function getData() {
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
return request(options)
.then(function(apires){
var obj = JSON.parse(apires);
var findMorePromises = obj.data.map(function(entry) {
return findMore(entry.id)
});
return Promise.all(findMorePromises);
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
return request(options)
.then(function(apires){
return JSON.parse(apires);
})
}
I've used map to construct the array of promises, but you could just as well use a foreach and push into an array similar to be more similar to your example code.
It's also good practice to make sure you are handling rejection of any promises (via catch), but I'll assume that is out of the scope of this question.
You want to use Promise.all.
So first thing first, you need an array of promises. Inside your for each loop, set findMore to a variable, and make it return the promise. Then have a line where you do Promise.all(promiseArr).then(function(){console.log("done)})
Your code would look like this
function getData() {
var promiseArr = []
var options = {
method: 'GET',
uri: 'https://api.call1.com',
qs: {
access_token: _accessToken,
}
};
request(options).then(function(apires){
console.log("complete 1");
var obj = JSON.parse(apires);
obj.data.forEach(function(entry) {
var p = findMore(entry.id)
promiseArr.push(p)
});
}).then(function(){
Promise.all(promiseArr).then(function(){
console.log("this is all done")
})
})
}
function findMore(id) {
var options = {
method: 'GET',
uri: 'https://api.call2.com',
qs: {
access_token: _accessToken,
}
};
return request(options).then(function(apires){
console.log("complete 2");
var obj = JSON.parse(apires);
})
}
the basic idea of Promise.all is that it only executes once all promises in the array have been resolved, or when any of the promises fail. You can read more about it here
You need to use Promise.all to run all async requests in parallel. Also you must return the result of findMore and getData (they are promises).
function getData() {
var options = {...};
return request(options)
.then(function(apires) {
console.log("complete 1");
var obj = JSON.parse(apires);
var ops = obj.data.map(function(entry) {
return findMore(entry.id);
});
return Promise.all(ops);
}
function findMore(id) {
var options = {...};
return request(options)
.then(function(apires) {
console.log("complete 2");
return JSON.parse(apires);
});
}
getData()
.then(data => console.log(data))
.catch(err => console.log(err));
If you can use ES7, it can be written with async/await:
let getData = async () => {
let options = {...};
let res = awit request(options);
let ops = res.data.map(entry => findMore(entry.id));
let data = await Promise.all(ops);
return data;
};
let findMore = async (id) => {
let options = {...};
let apires = awit request(options);
return JSON.parse(apires);
};
EDIT: As others have mentioned, using a Promise.all() is likely a better solution in this case.
If you are open to using jQuery (a JavaScript library), then you can use the .ajaxStop() event handler and specify your own function. Sample code:
$(document).ajaxStop(function(){
alert("All AJAX requests are completed.");
});
You will need to include the jQuery module. The instructions for Node.js are:
Install module through npm:
npm install jquery
Then use a "require" to use jQuery in your JavaScript code (a window with a document is required but there is no such "window" in Node so you can mock one with jsdom), see npm - jQuery for details:
require("jsdom").env("", function(err, window) {
if (err) {
console.error(err);
return;
}
var $ = require("jquery")(window);
});
If you want to stick to a pure JavaScript approach, you will need to create your own "module" to keep track of AJAX requests. In this module you can keep track of how many pending requests there are and remove them once they are terminated. Please see: Check when all Ajax Requests are complete - Pure JavaScript for more details.

Why does fetch return a weird hash of integers?

I'm using fetch API with React Native.
My response follows a normal format of {"message": "error here"} if the status is >= 400, which I will show in a native popup.
I'm trying to call response.json() after detecting a failure, but it keeps putting everything in a weird format...
{ _45: 0, _81: 0, _65: null, _54: null }
For whatever reason... the actual response I want is located in _65... I have no idea what these random keys are.
So currently I'm having to access it via _bodyText, but I assume that is wrong because it's a private underscore method.
What am I doing wrong?
var API = (function() {
var base = 'https://example.com/api/v1';
var defaults = {
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
var alertFailure = function(response) {
if (response.status >= 200 && response.status < 400) {
return response;
} else {
var json = JSON.parse(response._bodyText || '{}');
var message = json.message || 'There was a problem. Close the app, and try again later.';
var error = new Error(message);
error.response = response;
throw error;
}
};
var callAPI = function(url, opts) {
opts.headers['X-Version'] = 'v' + Package.version;
return fetch(base + url, opts)
.then(alertFailure)
.then((response) => {
return response.json();
})
.catch((error) => {
Alert.alert(null, error.message);
});
};
return {
get: function(url, opts) {
var fullOpts = Object.assign({}, defaults, opts);
return callAPI(url, fullOpts);
},
post: function(url, data, opts) {
var fullOpts = Object.assign({}, defaults, {
method: 'POST',
body: JSON.stringify(data || {})
}, opts);
return callAPI(url, fullOpts);
}
};
})();
The answer is that .json() returns a promise... so I had to do everything from within .then()
AsyncStorage.getItems always returns a promise. You can use this method below
AsyncStorage.getItem("access_key").then((value)=>
{
console.log(value);
});
I would recommend you to use the new ES7 syntax async/await, they are easier to understand than using .then()
To use it, just declare some method with the async prefix and inside of it use await to wait for the call to finish.
E.g
async someMethod(){
result = await fetch(URL); // Do some request to any API.
// Use the result after that knowing that you'll have it completed.
}
I hope this helps, at least in my opinion, I find this easier than using .then(), especially when you have to do multiple calls within the same method.

Categories

Resources