Le problème d'origine GitHub lié à l'annulation d'une récupération était depuis 2015. Maintenant, si je prends 2015 de 2017 (l'année en cours), j'obtiens 2. Cela démontre une en maths, car 2015 était en fait "pour toujours" auparavant.
En 2015, nous avons commencé à envisager l'abandon des récupérations en cours. Après 780 commentaires GitHub, et cinq requêtes d'extraction, nous disposons enfin d'une destination de récupération abandon possible dans les navigateurs. le premier étant Firefox 57.
Information:Noooope, je me trompais. Edge 16 s'est d'abord atterri avec la prise en charge de l'abandon en premier ! Félicitations au Équipe Edge !
Je reviendrai sur l'historique plus tard, mais tout d'abord, l'API:
Manœuvre manette + signal
Découvrez AbortController
et AbortSignal
:
const controller = new AbortController();
const signal = controller.signal;
La manette n'a qu'une seule méthode:
controller.abort();
Dans ce cas, le signal reçoit une notification:
signal.addEventListener('abort', () => {
// Logs true:
console.log(signal.aborted);
});
Cette API est fournie par la norme DOM. Il s'agit de l'API complète. Il est délibérément générique, afin qu'il puisse être utilisé par d'autres normes Web et bibliothèques JavaScript.
Annuler les signaux et récupérer
La récupération peut prendre un AbortSignal
. Par exemple, voici comment définir un délai avant expiration de la récupération après 5
secondes:
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
});
Lorsque vous annulez une extraction, la requête et la réponse sont annulées. Par conséquent, toute lecture du corps de la réponse
(response.text()
, par exemple) est également annulée.
Voici une démonstration : au moment de la rédaction de ce document, le seul navigateur utilisé qui le prend en charge est Firefox 57. Accrochez-vous : personne ayant des compétences en conception n'a pas été impliqué. lors de la création de la démonstration.
Vous pouvez également transmettre le signal à un objet de requête, puis le transmettre à l'extraction:
const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });
fetch(request);
Cela fonctionne, car request.signal
est de type AbortSignal
.
Réagir à une récupération annulée
Lorsque vous annulez une opération asynchrone, la promesse est rejetée avec un DOMException
nommé AbortError
:
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
}).catch(err => {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Uh oh, an error!', err);
}
});
Souvent, il n'est pas souhaitable d'afficher un message d'erreur si l'utilisateur a annulé l'opération, car il ne s'agit pas "erreur" si vous faites avec succès ce que l’utilisateur a demandé. Pour éviter cela, utilisez une instruction "if" comme l'instruction ci-dessus pour gérer spécifiquement les erreurs d'abandon.
Voici un exemple qui donne à l'utilisateur un bouton pour charger du contenu et un bouton pour annuler. Si l'extraction erreurs, une erreur s'affiche, sauf qu'il s'agit d'une erreur d'annulation:
// This will allow us to abort the fetch.
let controller;
// Abort if the user clicks:
abortBtn.addEventListener('click', () => {
if (controller) controller.abort();
});
// Load the content:
loadBtn.addEventListener('click', async () => {
controller = new AbortController();
const signal = controller.signal;
// Prevent another click until this fetch is done
loadBtn.disabled = true;
abortBtn.disabled = false;
try {
// Fetch the content & use the signal for aborting
const response = await fetch(contentUrl, { signal });
// Add the content to the page
output.innerHTML = await response.text();
}
catch (err) {
// Avoid showing an error message if the fetch was aborted
if (err.name !== 'AbortError') {
output.textContent = "Oh no! Fetching failed.";
}
}
// These actions happen no matter how the fetch ends
loadBtn.disabled = false;
abortBtn.disabled = true;
});
Voici une démonstration : au moment de la rédaction de ce document, les seuls navigateurs pris en charge sont Edge 16 et Firefox 57.
Un signal, plusieurs récupérations
Un seul signal peut être utilisé pour annuler plusieurs extractions à la fois:
async function fetchStory({ signal } = {}) {
const storyResponse = await fetch('/story.json', { signal });
const data = await storyResponse.json();
const chapterFetches = data.chapterUrls.map(async url => {
const response = await fetch(url, { signal });
return response.text();
});
return Promise.all(chapterFetches);
}
Dans l'exemple ci-dessus, le même signal est utilisé pour la récupération initiale et pour le chapitre parallèle
ou de récupérations. Voici comment utiliser fetchStory
:
const controller = new AbortController();
const signal = controller.signal;
fetchStory({ signal }).then(chapters => {
console.log(chapters);
});
Dans ce cas, l'appel de controller.abort()
annule les extractions en cours.
L'avenir
Autres navigateurs
Edge a fait un excellent travail pour lancer ce premier, et Firefox est sur la bonne voie. Ses ingénieurs implémentée à partir de la suite de tests alors que la spécification en cours d'écriture. Pour les autres navigateurs, procédez comme suit:
Dans un service worker
Je dois terminer les spécifications des pièces du service worker, mais voici le processus:
Comme je l'ai déjà mentionné, chaque objet Request
possède une propriété signal
. Dans un service worker,
fetchEvent.request.signal
signale un abandon si la page n'est plus intéressée par la réponse.
Par conséquent, un code comme celui-ci fonctionne:
addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
Si la page annule l'exploration, fetchEvent.request.signal
indique l'abandon. L'exploration dans le
et l'abandon du service worker.
Si vous récupérez autre chose que event.request
, vous devez transmettre le signal à votre
récupération(s) personnalisée(s).
addEventListener('fetch', event => {
const url = new URL(event.request.url);
if (event.request.method == 'GET' && url.pathname == '/about/') {
// Modify the URL
url.searchParams.set('from-service-worker', 'true');
// Fetch, but pass the signal through
event.respondWith(
fetch(url, { signal: event.request.signal })
);
}
});
Suivez les spécifications pour en effectuer le suivi. J'ajouterai des liens vers des tickets de navigateur une fois qu'il est prêt à être implémenté.
L'histoire
Oui. Il a fallu beaucoup de temps pour créer cette API relativement simple. Voici pourquoi :
Non-respect des règles concernant l'API
Comme vous pouvez le voir, la discussion GitHub est assez longue.
Ce fil de discussion comporte de nombreuses nuances (et quelques nuances), mais le principal désaccord est
voulait que la méthode abort
existe sur l'objet renvoyé par fetch()
, tandis que les autres
voulait une séparation entre l'obtention
de la réponse et l'incidence sur la réponse.
Ces exigences étant incompatibles, un groupe n’obtiendrait pas ce qu’il voulait. Si c'est
Désolé ! Si ça te rassure, j'étais aussi dans ce groupe. Toutefois, voir AbortSignal
correspond
des exigences d'autres API font que cela semble être le bon choix. De plus, autoriser les promesses
enchaînées à
devenir avorables deviendrait
très compliqué, voire impossible.
Si vous souhaitez renvoyer un objet qui fournit une réponse, mais que vous pouvez également annuler l'opération, vous pouvez créer un wrapper simple:
function abortableFetch(request, opts) {
const controller = new AbortController();
const signal = controller.signal;
return {
abort: () => controller.abort(),
ready: fetch(request, { ...opts, signal })
};
}
Les faux débuts dans TC39
Nous avons fait en sorte qu'une action annulée soit distincte d'une erreur. Cela comprenait une troisième promesse pour indiquer "cancelled" et une nouvelle syntaxe pour gérer l'annulation à la fois en mode synchrone et asynchrone. code:
Code non réel : la proposition a été retirée
try { // Start spinner, then: await someAction(); } catch cancel (reason) { // Maybe do nothing? } catch (err) { // Show error message } finally { // Stop spinner }
La chose la plus courante lorsqu'une action est annulée, c'est n'importe quoi. La proposition ci-dessus a séparé
de l'annulation des erreurs afin que vous n'ayez pas à gérer spécifiquement les erreurs d'abandon. catch cancel
permet
vous entendez parler d'actions annulées, mais la plupart du temps, vous n'en avez pas besoin.
Cela a atteint l'étape 1 de TC39, mais le consensus n'a pas été obtenu et la proposition a été retirée.
Notre proposition alternative, AbortController
, ne nécessitait pas de nouvelle syntaxe, elle n'avait donc aucun sens
pour le spécifier dans TC39. Tout ce dont nous avions besoin dans JavaScript était déjà présent. Nous avons donc défini
au sein de la plate-forme Web, en particulier la norme DOM. Une fois cette décision prise,
le reste est venu assez rapidement.
Modification importante des spécifications
Abandon de XMLHttpRequest
depuis des années, les spécifications étaient assez vagues. Ce n’était pas clair à
qui indique que l'activité réseau sous-jacente pourrait être évitée, arrêtée, ou que s'est-il passé si
une condition de concurrence s'est produite entre l'appel de abort()
et la fin de la récupération.
Nous voulions faire les bons choix cette fois-ci, mais cela s'est traduit par d'importants changements de spécifications qui nécessitaient (c'est ma faute. Merci beaucoup à Anne van Kesteren et Domenic Denicola de m'avoir entraîné dans cette vidéo) et de bonne série de tests.
Mais on y est maintenant ! Nous disposons d'une nouvelle primitive Web pour l'annulation des actions asynchrones. Plusieurs extractions peuvent être contrôlé en même temps ! Plus loin, nous verrons comment activer les modifications prioritaires tout au long du cycle d'exploration, ainsi qu'un niveau de priorité plus élevé. API pour observer la progression de la récupération.