I'm trying to migrate my API calls from using jQuery ajax to using the Fetch API.
I was using jQuery ajaxStart and ajaxStop to display a loading spinner during server calls.
I'm running several parallel server requests, I want the spinner to start when the first request start and to stop when the last request settled.
It was pretty straight foward with jQuery. However, I can't find a similiar technique using the fetch API.
Any ideas ?
You can use Promise to notify when fetch is called and completed
var params = {
a: 1,
b: 2
};
var data = new FormData();
data.append("json", JSON.stringify(params));
var currentRequest = new Request("/echo/json/", {
method: "POST",
body: data
});
var start, complete;
var fetchStart = new Promise(function(_start) {
start = _start;
})
var fetchComplete = new Promise(function(_complete) {
complete = _complete;
});
// do stuff when `fetch` is called
fetchStart.then(function(startData) {
console.log(startData)
});
// do stuff when `fetch` completes
fetchComplete.then(function(completeData) {
console.log(completeData)
});
var request = fetch(currentRequest);
[request, start({
"fetchStarted": currentRequest
})].shift()
.then(function(res) {
if (res.ok) {
// resolve `fetchComplete` `Promise` when `fetch` completes
complete({
"fetchCompleted": res
})
};
return res.json();
})
.then(function(data) {
document.body.textContent = JSON.stringify(data)
})
.catch(function(err) {
// if error, pass error to `fetchComplete`
complete({
"fetchCompleted": res,
"error": err
});
});
jsfiddle https://jsfiddle.net/abbpbah4/18/
See also Fetch: POST json data
Just start your spinner before the call to fetch and stop it in the finally
state.showSpinner = true;
Promise.all([fetch(url1), fetch(url2)])
.then(success => console.log(success))
.catch(error => console.error(error))
.finally(() => state.showSpinner = false);
Related
I am trying to create a function with a GET request that returns a portion of the data from the GET request. However, it keeps returning before the data is retrieved, so I keep getting "undefined". How can I set this up so it actually waits for the data to be set before returning?
let getInfo = async () => {
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => { // Promise being here DOES work
request.on('response', (response) => {
response.on('data', (chunk) => {
//return new Promise((resolve, reject) => { //Promise being here does NOT work
let body = JSON.parse(chunk)
let info = body.data
if (info){
resolve(info);
}
reject();
//})
});
});
request.write('')
request.end()
}).then(data => {
console.log("From then: "+data)
return data
})
}
getInfo().then(data => {
console.log("From outside: "+data)
})
Edit: This is the updated version that still does not work. I am trying to use the native electron method and I don't see why this doesn't work. The "From then:" part displays the info correctly. But when run "From outside:" it prints undefined. Does the issue have anything to do with the response.on being nested inside the request.on?
Solution: As #NidhinDavid showed in his answer, the issue was that the promise was inside the 'response' listener. Moving the 'GET' request from start to finish inside the Promise fixed it to giving the correct output. I have updated my code to reflect that for future individuals.
let getInfo = () => {
let info;
const request = net.request({
url: URL
})
return new Promise((resolve, reject) => {
request.on('response', (response) => {
response.on('data', (chunk) => {
request.write('')
request.end()
let body = JSON.parse(chunk)
info = body.data
if (info) {
resolve(info)
} else {
reject('Something went wrong');
}
});
});
})
}
getInfo()
.then(data => {
// this will be your info object
console.log(data)
})
.catch(err => {
// this will log 'Something went wrong' in case of any error
console.log(err)
})
You need to return inside your, on type event handler. Read more about asynchronous code and synchronous code here
I couldn't find the net module and the one which is included with Nodejs do not have request method. So to get the similar concept of event emiters and promise I am using http module and doing a http request to fetch json and parse it
'use strict'
var https = require('https');
const getInfo = async () => {
// create a new promise chain
// remember it is a chain, if one return is omitted
// then the chain is broken
return new Promise((resolve, reject) => {
var options = {
host: 'support.oneskyapp.com',
path: '/hc/en-us/article_attachments/202761727/example_2.json'
};
// start the request
https.request(options, function (response) {
var str = '';
// data arrives in chunks
// chunks needs to be stitched together before parsing
response.on('data', function (chunk) {
str += chunk;
});
// response body obtained
// resolve (aka return) the result
// or parse it, or do whatever you want with it
response.on('end', function () {
resolve(str)
});
// errors are another event
// listen for errors and reject when they are encountered
response.on('error', function (err) {
reject(err)
})
}).end()
})
}
//*********************************************
// using async await
//*********************************************
// if this is the entry point into app
// then top-level async approach required
(async ()=>{
try{
let data = await getInfo()
console.log("From ASYNC AWAIT ")
console.log(JSON.stringify(JSON.parse(data)))
}
catch (err) {
console.log("operation failed, error: ", err)
}
})();
//************************************************
// using promise chains
//************************************************
getInfo()
.then((data)=>{
console.log("FROM PROMISE CHAIN ")
console.log(JSON.stringify(JSON.parse(data)))
})
.catch((err)=>{
console.log("operation failed, error: ", err)
})
Tyr this, it might works for you,
let info;
const getInfo = async (_url)=>{
const response = await fetch(_url);
const data = await response.json();
info = data;
} ;
const url = "some url";
getInfo(url);
console.log(info);
Async function always returns a promise, so either consume that promise or internally await the data and assign it to some variable.
Check for the valid data required in info by logging it to the console.
I have some code that basically calls fetch in Javascript. The third party services sometimes take too long to return a response and in an attempt to be more user-friendly, I want to be able to either post a message or stop the connection from being open after N milliseconds.
I had recently come across this post:
Skip the function if executing time too long. JavaScript
But did not have much luck and had issues getting it to work with the below code. I was also hoping that there was a more modern approach to do such a task, maybe using async/await?
module.exports = (url, { ...options } = {}) => {
return fetch(url, {
...options
})
}
You can use a combination of Promise.race and AbortController, here is an example:
function get(url, timeout) {
const controller = new AbortController();
return Promise.race([fetch(url, {
signal: controller.signal
}), new Promise(resolve => {
setTimeout(() => {
resolve("request was not fulfilled in time");
controller.abort();
}, timeout)
})]);
}
(async() => {
const result = await get("https://example.com", 1);
console.log(result);
})();
The native Fetch API doesn't have a timeout built in like something like axios does, but you can always create a wrapper function that wraps the fetch call to implement this.
Here is an example:
const fetchWithTimeout = (timeout, fetchConfig) => {
const FETCH_TIMEOUT = timeout || 5000;
let didTimeOut = false;
return new Promise(function(resolve, reject) {
const timeout = setTimeout(function() {
didTimeOut = true;
reject(new Error('Request timed out'));
}, FETCH_TIMEOUT);
fetch('url', fetchConfig)
.then(function(response) {
// cleanup timeout
clearTimeout(timeout);
if(!didTimeOut) {
// fetch request was good
resolve(response);
}
})
.catch(function(err) {
// Rejection already happened with setTimeout
if(didTimeOut) return;
// Reject with error
reject(err);
});
})
.then(function() {
// Request success and no timeout
})
.catch(function(err) {
//error
});
}
from here https://davidwalsh.name/fetch-timeout
I need to implement a cancel-able client-side HTTP request in Node.js, without using external libraries. I'm giving a Promise object - cancellationPromise - which gets rejected when the cancellation is externally requested. This is how I know I may need to call request.abort().
The question is, should I be calling request.abort() only if https.request is still pending and response object is not yet available?
Or, should I be calling it even if I already got the response object and am processing the response data, like in the code below? In which case, will that stop any more response.on('data') events from coming?
async simpleHttpRequest(url, oauthToken, cancellationPromise) {
let cancelled = null;
let oncancel = null;
cancellationPromise.catch(error => {
cancelled = error; oncancel && oncancel(error) });
try {
const response = await new Promise((resolve, reject) => {
const request = https.request(
url.toString(),
{
method: 'GET',
headers: { 'Authorization': `Bearer ${oauthToken}` }
},
resolve);
oncancel = error => request.abort();
request.on('error', reject);
request.end();
});
if (cancelled) throw cancelled;
// do I need "oncancel = null" here?
// or should I still allow to call request.abort() while fetching the response's data?
// oncancel = null;
try {
// read the response
const chunks = await new Promise((resolve, reject) => {
response.on('error', reject);
const chunks = [];
response.on('data', data => chunks.push(data));
response.on('end', () => resolve(chunks));
});
if (cancelled) throw cancelled;
const data = JSON.parse(chunks.join(''));
return data;
}
finally {
response.resume();
}
}
finally {
oncancel = null;
}
}
It depends what you want to achieve by aborting a request.
Just a bit of background. HTTP 1 is not able to "cancel" a request it sends it and then waits for the response. You cannot "roll back" the request you did. You need a distributed transaction to do so. (Further reading.) As the MDN developer document states:
The XMLHttpRequest.abort() method aborts the request if it has already been sent. When a request is aborted, its readyState is changed to XMLHttpRequest.UNSENT (0) and the request's status code is set to 0.
Basically you stop the response from being processed by your application. The other application will probably (if you called abort() after it was sent to it) finish its processing anyways.
From the perspective of the question:
The question is, should I be calling request.abort() only if https.request is still pending and response object is not yet available?
TL.DR.: It only matters from the point of view of your application. As I glance at your code, I think it will work fine.
Here's an example from MDN below. There are two buttons. One is sending a request and another is canceling.
var controller = new AbortController();
var signal = controller.signal;
var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');
downloadBtn.addEventListener('click', fetchVideo);
abortBtn.addEventListener('click', function() {
controller.abort();
console.log('Download aborted');
});
function fetchVideo() {
...
fetch(url, {signal}).then(function(response) {
...
}).catch(function(e) {
reports.textContent = 'Download error: ' + e.message;
})
}
Now my case is different. I have a query parameter and if fetching is in progress but not finished and query parameter changes - how do I send a new request automatically canceling previous one?
Not sure if I got it clearly, but from what I understand, your case is not different no.
The basics are the same, store the controller somewhere accessible to your logic that may cancel it, and cancel it if needed, before sending the new request:
let aborter = null; // make the aborter accessible
function getData(param) {
// cancel pending request if any
if(aborter) aborter.abort();
// make our request cancellable
aborter = new AbortController();
const signal = aborter.signal;
const url = 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?rand=' + param;
return fetch(url, {signal})
// clean up, not really needed but may help to know if there is a pending request
.then(r => {aborter = null; return r;})
}
// first request will get aborted
getData("foo")
.then(r => console.log('foo done'))
.catch(e => console.error('foo failed', e.name, e.message));
// will abort the previous one
getData("bar")
.then(r => console.log('bar done'))
.catch(e => console.error('bar failed', e.name, e.message))
Using Angular 4 how do I make a http promise call inside of an Observable? I am using AWS API Gateway with Cognito Federated accounts. We have to use the apigClient.invokeApi to sign each http call. I want to get the results of promise call and return the result as an observable. The code below fires but does not get to the rest of the observable code at all. When I subscribe to the observable it never gets to the code inside of the subscribe. I am not getting any compile errors either. Any help would be helpful.
public upload(image, fileType): Observable<FileReturnData> {
const apigClient = apigClientFactory.newClient({
accessKey: this.auth.cognitoAccessKey,
secretKey: this.auth.cognitoSecretKey,
sessionToken: this.auth.cognitoSessionToken,
invokeUrl: this.auth.URL
});
const data = new FileData()
data.image = image;
data.fileType = fileType;
const uploadPromise = apigClient.invokeApi({}, '/users/upload', 'POST', {}, {image: image, fileType: fileType});
const observable = new Observable<FileReturnData>(observer => {
uploadPromise.then(function (uploadResult) {
console.log(uploadResult); // I see this in the console
const fileReturnData = new FileReturnData();
const responseBody = JSON.parse(uploadResult.body);
fileReturnData.filename = responseBody.filename;
console.log('IN OBSERVABLE'); // I never get to this result
observer.next(fileReturnData);
observer.complete();
return () => console.log('upload image user observable disposed');
}).catch(function (uploadImageError) {
return () => console.error(uploadImageError);
});
});
return observable;
}
I only use nested promises with Ajax calls like this but I believe your issue with your code, using observables and a nested promise is that you prematurely start the invokeApi http call, the call to /users/upload is not completing when you expect it to, thus subscribers of the observable returned by upload awaiting a observer.complete() will have unintended consequences because you are calling observer.complete() in uploadPromise.then which may have already completed or my not have completed yet.
See if this fixes your code:
public upload(image, fileType): Observable<FileReturnData> {
const apigClient = apigClientFactory.newClient({
accessKey: this.auth.cognitoAccessKey,
secretKey: this.auth.cognitoSecretKey,
sessionToken: this.auth.cognitoSessionToken,
invokeUrl: this.auth.URL
});
const observable = new Observable<FileReturnData>(observer => {
const data = new FileData()
data.image = image;
data.fileType = fileType;
apigClient.invokeApi({}, '/users/upload', 'POST', {}, {image: image, fileType: fileType})
.then(function (uploadResult) {
console.log(uploadResult); // I see this in the console
const fileReturnData = new FileReturnData();
const responseBody = JSON.parse(uploadResult.body);
fileReturnData.filename = responseBody.filename;
console.log('IN OBSERVABLE'); // I never get to this result
observer.next(fileReturnData);
observer.complete();
}).catch(function (uploadImageError) {
//handle error
});
});
return observable;
}
I personally find using nested promises for http calls easier to manage if I am understanding your scenario entirely. Here is a version of your code you can use, that uses promises only and then any Observable can call upload and await its completion:
public upload(image, fileType): Promise<FileReturnData> {
const apigClient = apigClientFactory.newClient({
accessKey: this.auth.cognitoAccessKey,
secretKey: this.auth.cognitoSecretKey,
sessionToken: this.auth.cognitoSessionToken,
invokeUrl: this.auth.URL
});
const promise = new Promise<FileReturnData>((resolve, reject) => {
const data = new FileData()
data.image = image;
data.fileType = fileType;
//I moved this "invokeApi" call here because it does not seem like a factory method
//it seems like you start an ajax call when "invokeApi" is called
//so you must keep this all within the promise you are returning to callers
//of the upload function
apigClient.invokeApi({}, '/users/upload', 'POST', {}, {image: image, fileType: fileType})
.then(uploadResult => {
console.log(uploadResult); // I see this in the console
const fileReturnData = new FileReturnData();
const responseBody = JSON.parse(uploadResult.body);
fileReturnData.filename = responseBody.filename;
resolve(fileReturnData);
}, msg => {
reject(msg);
}).catch(ex => {
return () => console.error(ex);
});
});
return promise;
}