NextJS API Route Returns Before Data Received? - async-await

I'm not sure what's going on here. I have set up an API route in NextJS that returns before the data has been loaded. Can anyone point out any error here please?
I have this function that calls the data from makeRequest():
export async function getVendors() {
const vendors = await makeRequest(`Vendor.json`);
console.log({ vendors });
return vendors;
}
Then the route: /api/vendors.js
export default async (req, res) => {
const response = await getVendors();
return res.json(response);
};
And this is the makeRequest function:
const makeRequest = async (url) => {
// Get Auth Header
const axiosConfig = await getHeader();
// Intercept Rate Limited API Errors & Retry
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
await new Promise(function (res) {
setTimeout(function () {
res();
}, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
token[n] = null;
originalRequest._retry = true;
const refreshedHeader = await getHeader();
api.defaults.headers = refreshedHeader;
originalRequest.headers = refreshedHeader;
return Promise.resolve(api(originalRequest));
}
return Promise.reject(error);
}
);
// Call paginated API and return number of requests needed.
const getQueryCount = await api.get(url, axiosConfig).catch((error) => {
throw error;
});
const totalItems = parseInt(getQueryCount.data['#attributes'].count);
const queriesNeeded = Math.ceil(totalItems / 100);
// Loop through paginated API and push data to dataToReturn
const dataToReturn = [];
for (let i = 0; i < queriesNeeded; i++) {
setTimeout(async () => {
try {
const res = await api.get(`${url}?offset=${i * 100}`, axiosConfig);
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const { data } = res;
const arrayName = Object.keys(data)[1];
const selectedData = await data[arrayName];
selectedData.map((item) => {
dataToReturn.push(item);
});
if (i + 1 === queriesNeeded) {
console.log(dataToReturn);
return dataToReturn;
}
} catch (error) {
console.error(error);
}
}, 3000 * i);
}
};
The issue that I'm having is that getVendors() is returned before makeRequest() has finished getting the data.

Looks like your issue stems from your use of setTimeout. You're trying to return the data from inside the setTimeout call, and this won't work for a few reasons. So in this answer, I'll go over why I think it's not working as well as a potential solution for you.
setTimeout and the event loop
Take a look at this code snippet, what do you think will happen?
console.log('start')
setTimeout(() => console.log('timeout'), 1000)
console.log('end')
When you use setTimeout, the inner code is pulled out of the current event loop to run later. That's why end is logged before the timeout.
So when you use setTimeout to return the data, the function has already ended before the code inside the timeout even starts.
If you're new to the event loop, here's a really great talk: https://youtu.be/cCOL7MC4Pl0
returning inside setTimeout
However, there's another fundamental problem here. And it's that data returned inside of the setTimeout is the return value of the setTimeout function, not your parent function. Try running this, what do you think will happen?
const foo = () => {
setTimeout(() => {
return 'foo timeout'
}, 1000)
}
const bar = () => {
setTimeout(() => {
return 'bar timeout'
}, 1000)
return 'bar'
}
console.log(foo())
console.log(bar())
This is a result of a) the event loop mentioned above, and b) inside of the setTimeout, you're creating a new function with a new scope.
The solution
If you really need the setTimeout at the end, use a Promise. With a Promise, you can use the resolve parameter to resolve the outer promise from within the setTimeout.
const foo = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 1000)
})
}
const wrapper = async () => {
const returnedValue = await foo()
console.log(returnedValue)
}
wrapper()
Quick note
Since you're calling the setTimeout inside of an async function, you will likely want to move the setTimeout into it's own function. Otherwise, you are returning a nested promise.
// don't do this
const foo = async () => {
return new Promise((resolve) => resolve(true))
}
// because then the result is a promise
const result = await foo()
const trueResult = await result()

Related

How can I use multiple await in cypress?

I am trying to fetch multiple data from UI using cypress.
First of all, I thought my selector is incorrect, but I have tried it with the same selector as the below codebase, but it still not working
below is the function
async getData(model?: any) {
const listSelector='[ng-repeat="workflow in vm.workflows track by $index"]';
const dataFromUi = {};
const data = await cy.get(listSelector); // this gives data
const data1 = await cy.get(listSelector); // this doesn't
dataFromUi['Test1'] = data;
dataFromUi['Test2'] = data1;
debugger;
return dataFromUi;
}
I was calling this method from a spec file, below is the calling spec file
describe('app test', () => {
beforeEach(() => {
cy.login();
cy.navigateUsingMenu('dasboard', '');
});
it('',async ()=>{
const result = await getData();
result.Test1 // contains data
result.Test2 // contains undefined
})
})
In data I am getting contents, but data1 returns undefined.
I have found a solution and that is using different methods and different it blocks. The below codebase is working, but I want them in a single block.
The service methods
async getData1(model?: any) {
const listSelector='[ng-repeat="workflow in vm.workflows track by $index"]';
const data = await cy.get(ListSelector);
return data;
}
async getData2(model?: any) {
const listSelector='[ng-repeat="workflow in vm.workflows track by $index"]';
const data1 = await cy.get(ListSelector);
return data1;
}
The spec
describe('app test', () => {
beforeEach(() => {
cy.login();
cy.navigateUsingMenu('dashboard', '');
});
it('',async ()=>{
const result = await getData1();
})
it('',async ()=>{
const result = await getData2();
})
})

how to unsubscribe from an observable returned from Observable.create in rxjs 5.1

I have this code:
async download(fileToUpload: UploadedFileMetaData): Promise<Observable<DownloadEvent>> {
const url = await this.getDownloadUrl(fileToUpload);
let xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
return Observable.create((observer) => {
console.log(observer);
xhr.open('GET', url);
xhr.send();
xhr.addEventListener('progress', (progress) => {
let percentCompleted;
That returns an ovservable.
I then use it like this:
const downloadSubscription = await this.blobStorageService.download(file);
downloadSubscription.subscribe((event) => // do stuff
Multiples of these might be created.
How do I unsubscribe?
You can store the subscribe() method return value which is a Subscriber object in a variable and call the unsubscribe() method when you want to unsubscribe.
const downloadSub = downloadSubscription.subscribe((event) => {});
downloadSub.unsubscribe();
In the end I went with this:
const destroy$ = new Subject<boolean>();
downloadSubscription.takeUntil(destroy$).subscribe(
// main body
},
(downloadEvent: FailureDownloadEvent) => {
// error
destroy$.next(true);
destroy$.unsubscribe();
},
() => {
// cleanup
destroy$.next(true);
destroy$.unsubscribe();
}
);
}

Promise all issue ( I get empty response )

How can I solve it? When I try to get a result from promise all, I get an empty array.
async function getMessage(arr) {
let response = [];
for (let count = 0; count < arr.length; count++) {
let protocol = await arr[count].link.split(':')[0];
if (protocol === 'http') {
await http.get(arr[count].link, async (res) => {
response.push(`${arr[count].link} - ${res.statusCode}\n`);
});
} else {
await https.get(arr[count].link, async (res) => {
response.push(`${arr[count].link} - ${res.statusCode}\n`);
});
}
}
return Promise.all(response)
.then((data) => {
//data === []
return data;
});
}
The await operator is used to wait for a Promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
I assume you're using node's native http and https module. It's true they're async functions, but they cannot be used with await straight, since they're using callback not Promise.
Afaik, you can either manually "promisify" it with util.promisify, or use some 3rd party like isomorphic-fetch which already promisified it for you.
Example:
const sayHelloWithoutPromise = () => {
setTimeout(() => console.log('hello'), 0)
}
(async function() {
await sayHelloWithoutPromise()
console.log('world!')
})()
const sayHelloWithPromise = () => {
return new Promise(r =>
setTimeout(() => r(console.log('hello')), 0)
)
}
(async function() {
await sayHelloWithPromise()
console.log('world!')
})()

How to handle Google OAuth flow via redux-saga

I am trying to implement Google OAuth 2 with with redux saga.
I have a watcher in my saga listening for GOOGLE_AUTH action which then executes googleLogin
function *watchGoogleAuth() {
yield *takeLatest(GOOGLE_AUTH, googleLogin)
}
function *googleLogin() {
const id_token = yield call(GoogleSignIn);
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
The implementation for GoogleSignIn is in apis.js
export function GoogleSignIn() {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
return {
id_token: GoogleUser.getAuthResponse().id_token
};
},
(err) => {
console.log(err)
}
)
}
But saga doesn't seem to wait for the GoogleSignIn to complete. As soon as OAuth consent screen pops up, saga proceeds executing the console.log without waiting for google signin promise to return actual data.
Is there any better way to handle this situation? Thanks!
To expand on #HenrikR's answer, the generator will not wait unless it receives a promise.
export const GoogleSignIn = () => {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
return new Promise((resolve, reject) => {
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
resolve(GoogleUser.getAuthResponse().id_token);
},
(err) => {
reject(err);
}
);
});
};
Accordingly, you should wrap the yield statement in a try/catch. Simplified and somewhat lazy:
function *googleLogin() {
try {
const id_token = yield call(GoogleSignIn);
if (id_token) { /* Possibly with more checks and validations */
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
} catch (e) {
console.log(e);
}
}

fetch returning promise rather than value

Hopefully the code below communicates the problem clearly. The issue is that in the module which uses the get method of fetchData, the value being returned is the actual Promise, rather than the JSON as desired. Any thoughts on this?
// fetchData.js module
var _ = require('lodash');
function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
return fetch(endpoint1)
.then((endpoint1Response) => {
return endpoint1Response.json()
.then((endpoint1JSON) => {
return fetch(endpoint2)
.then((endpoint2Response) => {
return endpoint2Response.json()
.then((endpoint2JSON) => {
var data = _.merge({}, {json1: endpoint1JSON}, {json2: endpoint2JSON});
console.log('data in fetch', data); // this logs the json
return data;
});
});
});
});
}
exports.get = get;
// module which uses get method of fetchData get
var fetchData = require('fetchData');
var data = fetchData.get();
console.log('returned from fetchData', data); // this logs a Promise
Yes, that's exactly what's supposed to happen. The whole point of promises is that their result value is not immediately available and that doesn't change just because you're obtaining one from a separate module.
You can access the value like this:
var fetchData = require('fetchData');
fetchData.get().then(data =>
console.log('returned from fetchData', data);
);
Also note that you are using promises in a non-idiomatic way and creating a "tower of doom." This is much easier on the eyes and accomplishes the same thing:
function fetchJson(endpoint) {
return fetch(endpoint)
.then(endpointResponse => endpointResponse.json());
}
function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
return Promise.all([fetchJson(endpoint1), fetchJson(endpoint2)])
.then(responses => {
var data = { json1: responses[0], json2: responses[1] };
console.log('data in fetch', data); // this logs the json
return data;
});
}
Edit I haven't used async/await in JavaScript, but to answer your question, I presume this would work:
async function fetchJson(endpoint) {
var res = await fetch(endpoint);
return res.json();
}
async function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
var data = {
json1: await fetchJson(endpoint1),
json2: await fetchJson(endpoint2)
};
console.log('data in fetch', data); // this logs the json
return data;
}
// module which uses get method of fetchData get
async function main() {
var fetchData = require('fetchData');
var data = await fetchData.get();
console.log('returned from fetchData', data);
}
return main();

Resources