· Zen HuiFer · Learn · 8 min read
This JavaScript API is more powerful than you imagine!
Delve into the powerful, yet overlooked, JavaScript API AbortController for enhanced control over asynchronous tasks. Use it to gracefully cancel fetch requests, manage event listeners, and more without cumbersome try-catch blocks.
This JavaScript API is more powerful than you imagine!
Today, let’s talk about a powerful standard JavaScript API that may have been overlooked by you-AbortController
。
In the past, people mentionedAbortController
When it comes to interrupt requests, examples are usually given, and even the description given by MDN is like this:
howeverAbortController
The ability is not limited to this,AbortController
It is a global class in JavaScript that can be used to terminate any asynchronous operation. The usage method is as follows:
const controller = new AbortController();controller.signal;
controller.abort();
We create aAbortController
After the instance, two things will be obtained:
signal
Attribute, this is aAbortSignal
For instance, we can pass it to the API that needs to be interrupted to respond to the interrupt event and handle it accordingly, for example, by passing it tofetch()
The method can terminate this request;.abort()
Method, calling this method will triggersignal
Suspend the event and mark the signal as aborted.
We can monitor through surveillanceabort
Event, then implement termination based on specific logic:
controller.signal.addEventListener('abort', () => {
//Implement termination logic
});
Let’s learn about some support for facial cleansingAbortSignal
The standard JavaScript API.
usage
Event Monitor
We can provide a pause when adding event listenerssignal
In this way, when the termination occurs, the listener will automatically delete.
const controller = new AbortController();window.addEventListener('resize', listener, { signal: controller.signal });controller.abort();
If we callcontroller.abort()
Will come fromwindow
Delete in the middleresize
monitor. This is a very elegant way to handle event listeners, as we no longer need abstract listener functions to call themremoveEventListener()
。
// const listener = () => {}
// window.addEventListener('resize', listener)
// window.removeEventListener('resize', listener)const controller = new AbortController();
window.addEventListener('resize', () => {}, { signal: controller.signal });
controller.abort();
If different parts of the application are responsible for deleting listeners, pass aAbortController
Instances would be more convenient, and then I found that I could use a single onesignal
Delete multiple event listeners!
useEffect(() => {
const controller = new AbortController(); window.addEventListener('resize', handleResize, {
signal: controller.signal,
});
window.addEventListener('hashchange', handleHashChange, {
signal: controller.signal,
});
window.addEventListener('storage', handleStorageChange, {
signal: controller.signal,
}); return () => {
//Calling `. abort() ` will delete all associated event listeners
controller.abort();
};
}, []);
In the above example, I added auseEffect()
Hooks, which introduce event listeners with different purposes and logic. Then, in the cleaning function, I only need to call it oncecontroller.abort()
You can delete all added listeners, it’s still very useful!
Fetch request
fetch()
Functions also supportAbortSignal
Interrupt requests should also be includedAbortController
The most frequently used scenario.
oncesignal
Up thereabort
The event is triggered,fetch()
Request returned by functionPromise
It will be rejected, thereby terminating unfinished requests.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example of file upload</title>
</head>
<body>
<input type="file" id="fileInput" />
<button id="uploadButton">upload</button>
<button id="cancelButton">Cancel upload</button> <script>
function uploadFile(file) {
const controller = new AbortController(); //Pass the abort signal to the fetch request
const response = fetch('/upload', {
method: 'POST',
body: file,
signal: controller.signal,
}); return { response, controller };
} document.getElementById('uploadButton').addEventListener('click', () => {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0]; if (!file) {
alert( Please select a file );
return;
} const { response, controller } = uploadFile(file); response.then(res => res.json())
.then(data => {
console.log( File upload successful: , data);
})
.catch(err => {
if (err.name === 'AbortError') {
console.log( 'File upload canceled' );
} else {
console.error( File upload failed: , err);
}
}); //Save controller to cancel upload
window.currentUploadController = controller;
}); document.getElementById('cancelButton').addEventListener('click', () => {
if (window.currentUploadController) {
window.currentUploadController.abort();
console.log( I clicked the cancel upload button );
} else {
console.log( There is no ongoing upload operation );
}
});</script>
</body>
</html>
In the example aboveuploadFile()
The function initiated aPOST
Request, return associatedresponse
Promise and oneAbortController
For example, when the user clicks the cancel upload button, we canAbortController
The instance can terminate this request at any time.
In Node.js, it is composed ofhttp
The requests sent by the module are also supportedsignal
Attribute!
const http = require('http');
const { AbortController } = require('abort-controller');function makeRequest() {
const controller = new AbortController(); const options = {
hostname: 'example.com',
port: 80,
path: '/',
method: 'GET',
//Pass AbortSignal to the request
signal: controller.signal
}; const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
}); res.on('end', () => {
console.log('Response:', data);
});
}); req.on('error', (e) => {
if (e.name === 'AbortError') {
console.log( Request cancelled );
} else {
console.error( `Request encountered an issue:${e.message}` );
}
}); req.end(); //Simulate cancellation operations, such as canceling requests after 2 seconds
setTimeout(() => {
controller.abort();
}, 2000);
}makeRequest();
AbortSignal
Static methods of classes
AbortSignal
Classes also have some static methods that can simplify request processing in JavaScript.
AbortSignal.timeout
We can useAbortSignal.timeout()
As a shortcut, a static method creates a signal that triggers an abort event after a certain timeout period. If you only want to cancel a request after it expires, you don’t need to create oneAbortController
And now:
document.getElementById('fetchButton').addEventListener('click', () => {
const url = 'https://jsonplaceholder.typicode.com/posts/1'; //Example API Address fetch(url, {
//If the request exceeds 1700 milliseconds, it will automatically terminate
signal: AbortSignal.timeout(1700),
})
.then(response => {
if (!response.ok) {
throw new Error( Network response failed );
}
return response.json();
})
.then(data => {
console.log( Request successful: , data);
})
.catch(error => {
if (error.name === 'AbortError') {
console.error( Request timeout canceled: , error);
} else {
console.error( Request error: , error);
}
});
});
AbortSignal.any
be similar toPromise.race()
We can use the method of handling multiple promisesAbortSignal.any()
The static method combines multiple abort signals into one. Here is a specific example:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example of AbortSignal.any</title>
</head>
<body>
<button id="stopButton">Stop monitoring</button> <script>
const publicController = new AbortController();
const internalController = new AbortController(); //Create WebSocket connection
const socket = new WebSocket('wss://conardli.websocket.org'); //Triggered when WebSocket connection is opened
socket.addEventListener('open', () => {
console.log( WebSocket connection established );
socket.send('Hello WebSocket!');
}); //Process received messages
function handleMessage(event) {
console.log( Received message: , event.data);
} //Combine multiple abort signals into one using AbortSignal.any
socket.addEventListener('message', handleMessage, {
signal: AbortSignal.any([publicController.signal, internalController.signal]),
}); //Simulate cancellation operation
document.getElementById('stopButton').addEventListener('click', () => {
publicController.abort();
console.log( Stop monitoring message events );
}); //It can also be cancelled through the internal controller
setTimeout(() => {
internalController.abort();
console.log( Internal controller automatically suspends monitoring );
}, 5000);</script>
</body>
</html>
Created two
AbortController
Examples, namelypublicController
andinternalController
。Establish a connection using WebSocket and send a message after the connection is established.
For WebSocket
message
Add listeners to the event and use them throughAbortSignal.any
Combine two stop signals.Added a button on the page that will be called when clicked
publicController.abort()
Stop monitoring message events.Additionally, using
setTimeout
Simulated an internal controller that automatically terminates the listening operation after 5 seconds.
In this way, multiple abort signals can be flexibly combined, and when any one signal is triggered, the relevant event listener will be canceled.
Cancel Flow
We can still use itAbortController
andAbortSignal
To cancel the flow.
In the following example, we create aWritableStream
And through monitoringcontroller.signal
ofabort
Events are used to handle the termination of flow operations.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example of canceling flow operation</title>
</head>
<body>
<button id="cancelButton">Cancel write operation</button> <script>
async function example() {
const abortController = new AbortController(); const stream = new WritableStream({
write(chunk, controller) {
console.log( Writing: , chunk); //Monitor stop signal
controller.signal.addEventListener('abort', () => {
console.log( The write operation has been canceled );
//Process flow termination logic, such as clearing resources or notifying users
});
},
close() {
console.log( 'Write completed' );
},
abort(reason) {
console.warn( Write aborted: , reason);
}
}); const writer = stream.getWriter(); //Simulate write operation
writer.write( Data Block 1 );
writer.write( Data Block 2 ); //Save abortController for canceling operation
window.currentAbortController = abortController;
writer.releaseLock(); //Release the write lock before creating a new write operation //Monitor the click event of the cancel button
document.getElementById('cancelButton').addEventListener('click', async () => {
if (window.currentAbortController) {
await writer.abort();
window.currentAbortController.abort();
console.log( I clicked the cancel write operation button );
} else {
console.log( There are no ongoing write operations );
}
});
} example();</script>
</body>
</html>
WritableStream
The controller has been exposedsignal
Attribute, i.e. the sameAbortSignal
. This way, I can callwriter.abort()
This will flow inwrite()
In the methodcontroller.signal
An upward bubble triggers an abort event.
Suspend any logic
actuallyAbortController
The ability is not limited to this, we can use it to make any logic interruptible!
For example, in the following example, we willAbortController
Adding to Drizzle ORM transactions allows us to cancel multiple transactions at once.
import { TransactionRollbackError } from 'drizzle-orm';function makeCancelableTransaction(db) {
return (callback, options = {}) => {
return db.transaction((tx) => {
return new Promise((resolve, reject) => {
options.signal?.addEventListener('abort', async () => {
reject(new TransactionRollbackError());
}); return Promise.resolve(callback.call(this, tx)).then(resolve, reject);
});
});
};
}
makeCancelableTransaction()
The function accepts a database instance, returns a high-order transaction function, and can then accept an abortsignal
As a parameter.
Bysignal
Add on instanceabort
The listener of the event, I can know when the termination occurred. This event listener will be called when the termination event is triggered, that is, whencontroller.abort()
When called. Therefore, when a termination occurs, I can return aTransactionRollbackError
Error to roll back the entire transaction (this is equivalent to callingtx.rollback()
And throw the same error).
Then, we use it in Drizzle.
const db = drizzle(options);const controller = new AbortController();
const transaction = makeCancelableTransaction(db);await transaction(
async (tx) => {
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} - 100.00` })
.where(eq(users.name, 'Dan'));
await tx
.update(accounts)
.set({ balance: sql`${accounts.balance} + 100.00` })
.where(eq(users.name, 'Andrew'));
},
{ signal: controller.signal }
);
We calledmakeCancelableTransaction()
Tool function, and pass it indb
Create a custom interruptible transaction using an instance. From now on, I can use this custom transaction to perform multiple database operations as usual in Drizzle, and also provide an abort for itsignal
Cancel all operations at once.
Abort error handling
Each termination event is accompanied by a termination reason, which allows us to have more customization and respond differently to different termination reasons.
The reason for termination iscontroller.abort()
Optional parameters for the method. You can do it at any timeAbortSignal
Examples ofreason
Reason for access termination in attribute.
async function fetchData() {
const controller = new AbortController();
const signal = controller.signal; //Monitor the abort event and print the reason for the abort
signal.addEventListener('abort', () => {
console.log( Reason for request termination: , signal.reason); //Print custom termination reason
}); try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1', { signal });
const data = await response.json();
console.log( Request successful: , data);
} catch (error) {
if (error.name === 'AbortError') {
console.error( Request cancelled due to suspension: , error.message);
} else {
console.error( Request error: , error.message);
}
} //Save controller for canceling operation
window.currentAbortController = controller;
} fetchData(); //Monitor the click event of the cancel button
document.getElementById('cancelButton').addEventListener('click', () => {
if (window.currentAbortController) {
window.currentAbortController.abort( The user cancelled the request ); //Provide custom termination reasons
console.log( I clicked the cancel request button );
} else {
console.log( There are no ongoing requests );
}
});
reason
Parameters can be any JavaScript value, so we can pass strings, errors, and even objects.
compatibility
AbortController
The compatibility is very good, and it has been included in the Web Compatibility Baseline for a long time. Since March 2019, it can be used in all mainstream browsers.