Cancelling fetch

Hi Guys,

Just wondering if there is a way to cancel a fetch request?

Thanks,
Michael

Hey Michael!

Fetch request is an async operation, and AFAIK after you’ve launched it there is no direct way to make it stop. You have to wait for it to either return a result or an error when it’s done.

However, there are several things you can do to work around this limitation, but suggesting the right one will depend on why exactly do you think you need to cancel a request. Would you please share your use-case?

On such scenario I have come by, is when your app repeatedly sends identical requests to a backend. You might make your fetch only execute new calls when it’s done with the previous one, but that is very limiting.

This comes in handy when a user has the option to continuously press a “Refresh” button, and upon receiving the response you want to be sure that it’s a response to the last request you sent. Otherwise, you know, you might be populating the resulting list multiple times or simply with obsolete data.

So what I built for my own usage, was a tiny RequestPool class. It allows you to execute as many requests you like, and you have the possibility to act on only the last, most recent, one.

The usage goes like this:

function getSomethingFromAPI() {
	// first, create a request entry in the pool by supplying a unique 'action' descriptor
	// note that at this point, the .add() call kills all other ongoing requests with the same 'action' in the pool!
	var requestId = RequestPool.add('getSomething');
	// then launch the request
	fetch(/* your actuall fetch params here */)
	.then(function(response) {
		// here we check if that requestId is still alive
		if (RequestPool.is_alive(requestId)) {
			// got the right response, do something with it
		} else {
			// got a response to a request that is likely already dead, ignore it
		}
	}).catch(function(error) {
		// ...
	});
}

And the RequestPool class is as simple as this:

// request pool array, holds the active request IDs
var pool = [];

// this is how a single request looks like in the pool
function Request(action) {
	this.action = action;
	this.id = btoa(action + Date.now()); // base64-encoded action plus timestamp, makes for a nice unique hash
};

// only use this for exclusive calls that should remove the previous with the same action
function add(action) {
	// first kill all other requests with the same action
	for (var i = pool.length - 1; i >= 0; i--) {
		if (pool[i].action == action) {
			pool.splice(i, 1);
		}
	}
	// then create a new request and add it to the pool
	var req = new Request(action);
	pool.push(req);
	// return id
	return req.id;
};

function is_alive(request_id) {
	// loop through all of the pool
	for (var i in pool) {
		// check if the request id is there
		if (pool[i].id == request_id) {
			// kill the request and return true
			pool.splice(i, 1);
			return true;
		}
	}
	// no matching requests found, return false
	return false;
};

module.exports = {
	'add': add,
	'is_alive': is_alive
};

Hope this helps.

Hi Uldis,

Actually the request is for an image upload. The way the app works the user can select any image from the cameraroll and it will upload to the server. The user can upload up to 5 images at a time.

I want to implement a cancel function in case the user presses the wrong image or doesn’t want to wait any longer for extra images to be sent.

It seems strange that there wouldn’t be some form of abort. Is XMLHttpRequest not a ASYNC function as well?

Michael

Cancelable promises are still a long discussion but it’ll take a while for it to land into JS, I’m sure the fuse team could somehow provide a workaround.

@uldis you might want to look at Promise.race method

@michael for now this might be a way of handling this, just convert it to ES5 for it to run in Fuse