How to intercept all AJAX(xhr and fetch) requests via javascript? - javascript

I have xhr and fetch type of xhr requests and responses that I want to intercept.
FETCH API CALLS
I've tried
window.fetch = function() {
// Intercept the request here
}
and
XMLHttpRequest.prototype.send = function() {
// Intercept the request here
}
But I want to write one generic function which intercepts all the xhr requests and responses.

By default, fetch() doesn’t provide a way to intercept requests, but it’s not hard to come up with a workaround. You can overwrite the global fetch method and define your own interceptor, like this:
fetch = (originalFetch => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log('Request was sent'));
};
})(fetch);
fetch('https://api.github.com/orgs/axios')
.then(response => response.json())
.then(data => {
console.log(data)
});
`

Related

How to make AJAX request in Hackerrank using JavaScript?

I open the Hackerrank example test and play around with methods one might use to make an AJAX call. XMLHttpReq, fetch, etc. None of them work; XHR and fetch methods are unavailable.
First fetch:
async function myFetch() {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
let data = await response.json();
console.log(data);
}
Hackerrank throws an error because fetch is not a function. I also tried window.fetch and global.fetch to no avail.
I tried XHR:
function myXHR() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
console.log(this.responseText);
// or JSON.parse(this.responseText);
}
};
xmlhttp.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xmlhttp.send();
}
Hackerrank says XMLHttpRequest is not defined.
Hackerrank is executing Node.JS code, that explains why XHR isn't available, I have to require myself perhaps. Except I can't npm install anything, all I have access to is their little IDE.
How do you make an AJAX call in this platform with JavaScript?
I've passed the HackerRank REST API certification and had the same issue. HackerRank uses a NodeJs environnement to run you code (it's said in the langage selection), so neither XMLHttpRequest nor fetch are available ( as these are Browser only ).
I suggest you use the request npm package, HackerRank allows you to require it.
One downside is that request doesn't support Promises & Async/Await unless you import other packages (which HackerRank doesn't seem to recognize).
Here's what I used :
const request = require('request');
function myFetch(url) {
return new Promise((resolve, reject) => {
request(url, function (error, response, body) {
if(error) reject(error)
else resolve(body)
});
});
}
Note : request package has been recently deprecated, but it will still work well for your use case.
I have given a test in HackerRank recently and I have used the node.js native module for http/https to fetch data from an API. As you can use this without requiring any external libraries.
also, if you need to create promises you can create your own wrapping over the https implementation.
Try using this:
async function fetchData(url) {
const https = require('https');
return new Promise((resolve, reject) => {
https.get(url, (response) => {
let data = '';
response.on('data', (stream) => {
data += stream;
})
response.on('end', () => {
const resolvedData = JSON.parse(data);
resolve(data);
})
}).on('error', (err) => {
reject(err);
})
});
}
async function showData() {
const data = await fetchData('https://jsonmock.hackerrank.com/api/movies?Year=2000');
console.log(data);
}
showData();
this can solve your problem in HackerRank. This example is only given for a get request. For all other Methods please try using the options from https module of Node.js.
Hackerrank currently uses node version 14.x.xx which comes with axios.
All you have to do is scroll to the top and import or require axios, then you can make use of axios.get() or axios.post() as the case may be.
Hackerrank currently uses node version 14.x.xx which comes with Axios
Example for get call:
a) const axios = require('axios');
b) let response = await axios.get('URL here');
In the same way, you can use all HTTP Methods.
let url = 'https://someurl.com/api/article_users?username=username';
const https = require('https');
https.get(url, (res) => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
res.on('data', (d) => {
process.stdout.write(d);
});
}).on('error', (e) => {
console.error(e);
});
This worked for me.

How to cancel http request properly in Node.js?

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.

.then() fires before promise resolves

In my vanilla js project I have a following chain of promises:
API.POST({link to login endpoint}, "email=foo&pass=bar")
.then(() => User.initiate(API))
.then(() => console.log(User.name || 'wat'));
API object has POST and GET methods both looking the same, except the request type:
GET (url, params = null) {
console.log("GET start");
return new Promise((resolve,reject) => {
this._request('GET', url, params)
.then(result => resolve(result));
});
}
POST (url, params = null) {
return new Promise((resolve,reject) => {
this._request('POST', url, params)
.then(result => resolve(result));
});
}
... and a _request method responsible for sending the request:
_request (type, url, params = null) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open(type,url,true);
xhr.withCredentials = true;
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
xhr.onload = function(){
if (this.status == 200) {
console.log(type + ' done');
resolve(this.response);
} else {
reject(this.response._status.code);
}
};
xhr.send(params);
});
}
App's User object provides "initiate" method that calls the API to check if user is logged in. With positive response API returns an _embedded.user object, that in the next step is used to populate app's User properties:
initiate(API) {
API.GET({link to authorization check endpoint})
.then(authData => this.regenerate(authData._embedded.user));
},
regenerate(userData) {
this.name = userData.name;
// and so on, for each User property
return new Promise((resolve,reject) => {
resolve(this);
});
}
What I expect to happen is:
Request to API to log in is sent (this is just to skip the actual logging in process that is irrelevant to the current work)
API returns cookie allowing for further testing as an logged user
Request to API is sent to ask if user is authenticated
API responds with confirmation and _embedded.user object
App's User object's properties get populated by data from API response
User.name is logged in console
Step 6 though fires between steps 3. and 4. and I can't find the reason why. My console looks following (notice the console.logs in API object code above):
POST done
GET start
wut
GET done
What can be the reason of this? Thanks in advance.
Missing a return in initiate()
initiate(API) {
return API.GET({link to authorization check endpoint})
//^^ return the promise
.then(authData => this.regenerate(authData._embedded.user));
}
Also using a promise anti-pattern in your GET and POST methods. There is no need to create a new promise in each since _request() already returns a promise
All you need is:
GET (url, params = null) {
return this._request('GET', url, params);
}
POST (url, params = null) {
return this._request('POST', url, params);
}
For more detailed explanation see What is the explicit promise construction antipattern and how do I avoid it?
Rather than using XMLHttpRequest you may also want to look at using the more recent fetch() API which has built in promises and better error handling

How can I unit test a function that uses promises and event emitters in Node.js?

My question is about unit testing with promises and event emitters in Node.js. I am using the jasmine framework if that matters.
The code below uses the https module of Node.js to send a request to an API. The API will return JSON. The JSON from the API is the "rawData" variable in the code below.
I want to unit test that the function returns JSON (and not a JavaScript object).
I have unsuccessfully tried several approaches to unit testing that aspect of this function:
1) I tried spying on the Promise constructor so that it would return a fake function which would simply return a JSON string.
2) I have tried spying on the .on('eventType', callback) function of EventEmitters in Node.js to fake a function that returns JSON.
My question is: are either of the two approaches above possible and/or recommend for accomplishing my goal? Is there a different approach to isolating the http request and emitting of events from my unit test objective? Do I need to rewrite this function to facilitate easier unit testing?
const https = require('https');
function getJSON() {
return new Promise((resolve, reject) => {
const request = https.get(someConfig);
request.on('response', resolve);
})
.then(msg => {
return new Promise((resolve, reject) => {
let rawData = '';
msg.on('data', chunk => { rawData += chunk });
msg.on('end', () => {
resolve(rawData);
});
});
})
.then(json => {
JSON.parse(json);
return json;
})
}
Is there a reason you want to stick to https for making a request? If not, your code and your testing can both become really simple. I'll give an example using axios.
Http request can look like this
getJSON() {
const url = 'https://httpbin.org/get';
return axios
.get(url)
.then(response => response);
}
and you can stub the get call with Sinon
lab.experiment('Fake http call', () => {
lab.before((done) => {
Sinon
.stub(axios, 'get')
.resolves({ data: { url: 'testUrl' } });
done();
});
lab.test('should return the fake data', (done) => {
const result = requestHelper.getJSON2();
result.then((response) => {
expect(response.data.url).to.eqls('testUrl');
axios.get.restore();
done();
});
});
});
With the existing code, nock would work like this
lab.experiment('Fake http call with nock', () => {
lab.test('should return the fake data', (done) => {
nock('https://httpbin.org')
.get('/get')
.reply(200, {
origin: '1.1.1.1',
url: 'http://testUrl',
});
const result = requestHelper.getJSON2();
result.then((response) => {
const result = JSON.parse(response);
console.log(JSON.parse(response).url);
expect(result.url).to.eqls('http://testUrl');
nock.cleanAll();
done();
});
});
});
Full code is here
I would say that you need to refactor the code a little bit to be more testable.
When I write unit tests for functions I keep below points in mind
You do not need to test for the inbuilt or library modules as they are already well tested.
Always refactor your functions to have very specific reponsibility.
Implementing these two in your example, i would separate the server call in a service module whose sole responsibility is to take url (and configurations, if any) make server calls.
Now, when you do that you get two benefits
1. you have a reusable piece of code which you can now use to make other server calls(also makes your code cleaner and shorter)
Since its a module you can now write seperate tests for that module and take the responsibility of checking whether server calls are made from your current module that uses it.
Now all thats left to test in your getJSON function is to spyOn that service module and use tohaveBeenCalledWith and check that data is properly parsed.You can mock the service to return your desired data.
1 its making a service call
so test for toHaveBeenCalledWith
2 its parsing to JSON
so test for valid/invalid JSON
also test for failures
//no need to test whether https is working properly
//its already tested
const https = require('https');
const service = require("./pathToservice");
function getJSON() {
return service.get(somConfig)
.then(json => {
JSON.parse(json);
return json;
})
}
//its cleaner now
//plus testable
I think you have not succeeded because you're returning directly like that. It should be like:
function getJSON(callback) {
(new Promise((resolve, reject) => {
const request = https.get(someConfig);
request.on('response', resolve);
}))
.then(msg => {
return new Promise((resolve, reject) => {
let rawData = '';
msg.on('data', chunk => { rawData += chunk });
msg.on('end', () => {
resolve(rawData);
});
});
})
.then(json => {
JSON.parse(json);
callback(json);
})
}
// to use this:
getJSON((your_json)=> {
// handling your json here.
})
You can use child_process to spawn a test server to provide JSON API. Example:
const { spawn } = require('child_process');
const expect = chai.expect;
const env = Object.assign({}, process.env, { PORT: 5000 });
const child = spawn('node', ['test-api.js'], { env });
child.stdout.on('data', _ => {
// Make a request to our app
getJSON((foo)=>{
// your asserts go here.
expect(foo).to.be.a('object');
expect(foo.some_attribute).to.be.a('string')
// stop the server
child.kill();
});
});
You can custom your someConfig variable in test environment to point to 'http://127.0.0.1:5000'. your test-api.js file is a simple nodejs script that always response an expected JSON for every request.
Updated unit test example

$http simultaneous duplicate API call, return promise

In my app, we are using micro services structure, and for that we have aso added caching.
But there is a scenario in which multiple same request gets fired, and the request are fired at the same time, so caching also doesn't seem to work.
I want that for duplicate request it should send promise of previous request.
Also there are lots of services, so adding promise to each service will not work. Is there any place where to handle duplicate request and pass the previous request promise at a common place. might be by using decorator or http interceptor.
Service call
testService.getTestByIds = function (
testIds,
contextInfo) {
var params = {
testId: requestDefinitionIds
};
params = CommonProvider.apppendContextParams(contextInfo, params,testInfoCache);
return $http.get(SERVICE_URL + 'tests', {
cache: testInfoCache,
params: params,
transformResponse: CommonProvider.appendTransformer($http.defaults.transformResponse, function (value) {
return transformTestResponse(value);
})
});
};
I assume you mean caching on the client, you could try something like this:
const activeRequest =
() => {
const requests = {};
return (url,params) => {
const promise = requests[url+JSON.stringify(params)];
if(promise){
return promise;
}
return requests[url+JSON.stringify(params)] =
//not sure what you use for angular, maybe $http for angular1
fetch(url,params)
.then(
x=>{
requests[url+JSON.stringify(params)] = undefined;
return x;
}
)
}
}

Categories

Resources