"Getirme işlemini iptal etme" ile ilgili orijinal GitHub sorunu 2015'te açıldı. Şimdi 2017'den (mevcut yıl) 2015'i çıkardığımızda 2 elde ederiz. Bu, matematikte bir hata olduğunu gösterir. Çünkü 2015 aslında "çok uzun zaman önce"dir.
Devam eden getirme işlemlerini iptal etmeyi ilk kez 2015'te keşfetmeye başladık. 780 GitHub yorumu, birkaç yanlış başlangıç ve 5 çekme isteği sonrasında nihayet tarayıcılarda iptal edilebilir getirme özelliğini kullanıma sunduk. Bu özelliği ilk kullanan tarayıcı Firefox 57 oldu.
Güncelleme: Yanlıştı. Edge 16, iptal desteğiyle ilk kez kullanıma sunuldu. Edge ekibini tebrik ederiz.
Geçmişe daha sonra değineceğiz. Öncelikle API'den bahsedelim:
Denetleyici + sinyal manevrası
AbortController
ve AbortSignal
ile tanışın:
const controller = new AbortController();
const signal = controller.signal;
Denetleyicinin yalnızca bir yöntemi vardır:
controller.abort();
Bunu yaptığınızda sinyal aşağıdakileri bildirir:
signal.addEventListener('abort', () => {
// Logs true:
console.log(signal.aborted);
});
Bu API, DOM standardı tarafından sağlanır ve API'nin tamamı budur. Diğer web standartları ve JavaScript kitaplıkları tarafından kullanılabilmesi için kasıtlı olarak geneldir.
Sinyalleri iptal etme ve getirme
Getirme işlemi AbortSignal
sürebilir. Örneğin, 5 saniye sonra getirme zaman aşımı oluşturmak için aşağıdakileri yapmanız gerekir:
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);
});
Bir getirme işlemini iptal ettiğinizde hem istek hem de yanıt iptal edilir. Bu nedenle, yanıt gövdesinin (response.text()
gibi) okunması da iptal edilir.
Demoyu buradan inceleyebilirsiniz. Bu özelliği şu anda yalnızca Firefox 57 desteklemektedir. Ayrıca, demonun oluşturulmasında tasarım konusunda herhangi bir yetkinliğe sahip kimse yer almadı.
Alternatif olarak, sinyal bir istek nesnesine verilebilir ve daha sonra getirme işlemine iletilebilir:
const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });
fetch(request);
Bu, request.signal
bir AbortSignal
olduğu için işe yarar.
Kesilen getirme işlemine tepki verme
Bir asynkron işlemi iptal ettiğinizde söz, AbortError
adlı bir DOMException
ile reddedilir:
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);
}
});
Kullanıcının istediği işlemi başarıyla gerçekleştirdiğinizde "hata" olmadığından, kullanıcı işlemi iptal ederse genellikle hata mesajı göstermek istemezsiniz. Bunu önlemek için, yukarıdaki gibi bir if-statement kullanarak kesinti hatalarını özel olarak ele alın.
Kullanıcıya içerik yükleme ve işlemi iptal etme düğmesi sunan bir örneği aşağıda bulabilirsiniz. Getirme işleminde hata oluşursa (kesme hatası hariç) bir hata gösterilir:
// 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;
});
Demoyu buradan inceleyebilirsiniz. Bu özelliği şu anda yalnızca Edge 16 ve Firefox 57 desteklemektedir.
Tek sinyal, çok sayıda getirme
Tek bir sinyal, birden fazla getirme işlemini aynı anda iptal etmek için kullanılabilir:
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);
}
Yukarıdaki örnekte, ilk getirme ve paralel bölüm getirme işlemleri için aynı sinyal kullanılır. fetchStory
simgesini şu şekilde kullanabilirsiniz:
const controller = new AbortController();
const signal = controller.signal;
fetchStory({ signal }).then(chapters => {
console.log(chapters);
});
Bu durumda, controller.abort()
çağrısı, devam eden tüm getirme işlemlerini iptal eder.
Gelecek
Diğer tarayıcılar
Edge bu özelliği ilk kullanıma sunarak harika bir iş çıkardı ve Firefox da hemen ardından onları takip etti. Mühendisleri, spesifikasyon yazılırken test paketinden uygulama yaptı. Diğer tarayıcılar için takip edilmesi gereken destek kaydı numaraları:
Hizmet çalışanında
Hizmet çalışanı bölümlerinin spesifikasyonunu tamamlamam gerekiyor. Planımız şu şekilde:
Daha önce de belirttiğim gibi, her Request
nesnesinin bir signal
özelliği vardır. Bir hizmet çalışanı içinde, sayfa artık yanıtla ilgilenmiyorsa fetchEvent.request.signal
iptal sinyali gönderir.
Sonuç olarak, aşağıdaki gibi kodlar sorunsuz çalışır:
addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
Sayfa getirme işlemini iptal ederse fetchEvent.request.signal
iptal sinyali gönderir. Böylece hizmet çalışanındaki getirme işlemi de iptal edilir.
event.request
dışında bir öğe getiriyorsanız sinyali özel getirme işlemlerinize iletmeniz gerekir.
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 })
);
}
});
Bunu izlemek için özelliği takip edin. Uygulamaya hazır olduğunda tarayıcı biletlerine bağlantılar ekleyeceğim.
Geçmiş
Evet, bu nispeten basit API'nin oluşturulması uzun sürdü. Bunun nedenleri aşağıda açıklanmıştır:
API uyuşmazlığı
Gördüğünüz gibi, GitHub tartışması oldukça uzun.
Bu ileti dizisinde birçok ayrıntı (ve bazı ayrıntı eksiklikleri) var ancak temel anlaşmazlık, bir grubun abort
yönteminin fetch()
tarafından döndürülen nesnede bulunmasını istemesi, diğer grubun ise yanıtı alma ile yanıtı etkileme arasında bir ayrım olmasını istemesidir.
Bu şartlar uyumlu olmadığından bir grup istediği şeyi elde edemeyecekti. Bu sizseniz özür dileriz. Sizi rahatlatacaksa ben de bu gruptaydım. Ancak AbortSignal
'ün diğer API'lerin koşullarına uyduğunu görmek, doğru seçim olduğunu gösteriyor. Ayrıca, zincirlenmiş vaatlerin iptal edilebilir hale gelmesi imkansız olmasa da çok karmaşık olurdu.
Yanıt veren ancak iptal de edebilecek bir nesne döndürmek istiyorsanız basit bir sarmalayıcı oluşturabilirsiniz:
function abortableFetch(request, opts) {
const controller = new AbortController();
const signal = controller.signal;
return {
abort: () => controller.abort(),
ready: fetch(request, { ...opts, signal })
};
}
TC39'da yanlış başlangıçlar
İptal edilen bir işlemin hatadan farklı olması sağlandı. Bu değişiklikler arasında "iptal edildi" durumunu belirten üçüncü bir promise durumu ve hem senkronize hem de asynkron kodda iptal işlemini gerçekleştirmek için bazı yeni söz dizimi yer aldı:
Gerçek kod değil: Teklif geri çekildi
try { // Start spinner, then: await someAction(); } catch cancel (reason) { // Maybe do nothing? } catch (err) { // Show error message } finally { // Stop spinner }
Bir işlem iptal edildiğinde yapılması gereken en yaygın şey hiçbir şey yapmamaktır. Yukarıdaki öneri, iptal işlemini hatalardan ayırdı. Böylece, iptal hatalarını özel olarak ele almanız gerekmedi. catch cancel
, iptal edilen işlemler hakkında sizi bilgilendirir ancak çoğu durumda bu gerekmez.
Bu öneri TC39'da 1. aşamaya ulaştı ancak fikir birliği sağlanamadı ve teklif geri çekildi.
Alternatif önerimiz olan AbortController
için yeni söz dizimi gerekmediğinden TC39'da bu önerinin spesifikasyonunu oluşturmak anlamlı değildi. JavaScript'ten ihtiyaç duyduğumuz her şey zaten mevcuttu. Bu nedenle, web platformundaki arayüzleri, özellikle de DOM standardını tanımladık. Bu kararı verdikten sonra geri kalanı nispeten hızlı bir şekilde tamamladık.
Büyük özellik değişikliği
XMLHttpRequest
yıllardır iptal edilebilir durumda ancak spesifikasyon oldukça belirsizdi. Temel ağ etkinliğinin hangi noktalarda önlenebileceği veya sonlandırılabileceği ya da abort()
çağrılması ile getirme işleminin tamamlanması arasında bir yarış koşulu varsa ne olduğu net değildi.
Bu sefer doğru olanı yapmak istedik ancak bu, çok fazla inceleme gerektiren (bu benim hatam. Beni bu süreçten geçiren Anne van Kesteren ve Domenic Denicola'ya çok teşekkürler) ve bir dizi test gerektiren büyük bir özellik değişikliğiyle sonuçlandı.
Ancak şimdi buradayız. Asenkron işlemleri iptal etmek için yeni bir web ilkelimiz var ve birden fazla getirme işlemi aynı anda kontrol edilebilir. Gelecekte, getirme işleminin ömrü boyunca öncelik değişikliklerini ve getirme işleminin ilerleme durumunu gözlemlemek için daha üst düzey bir API'yi etkinleştirmeyi planlıyoruz.