İptal edilebilir getirme

Jake Archibald
Jake Archibald

"Getirme işlemini iptal etme" ile ilgili orijinal GitHub sorunu şuydu: 2015’te açıldı. 2015 ile 2017'nin (mevcut yıl) farkına varırsam 2 puan alıyorum. Bu durum, matematik hatası, çünkü 2015 aslında "sürekli" geçti. önce.

2015 yılında, devam eden getirme işlemlerini iptal etmeyi keşfetmeye başladığımızda ilk kez 780 GitHub yorumundan sonra çok sayıda yanlış başlatma ve 5 pull isteği içerir. Son olarak, tarayıcılarda İlki Firefox 57.

Güncelleme: Hayır, yanılmışım. Edge 16'da önce iptal desteği verildi. Başarılı bir proje için Edge ekibi!

Geçmişi daha sonra ele alacağım, ama önce API'yla ilgili olarak:

Kumanda ve sinyal manevrası

AbortController ve AbortSignal ile tanışın:

const controller = new AbortController();
const signal = controller.signal;

Kumandanın yalnızca bir yöntemi vardır:

controller.abort();

Bunu yaptığınızda sinyali bilgilendirir:

signal.addEventListener('abort', () => {
    // Logs true:
    console.log(signal.aborted);
});

Bu API, DOM standardı tarafından sağlanır ve API'nin tamamı budur. İnsanların diğer web standartları ve JavaScript kitaplıkları tarafından kullanılabilecek şekilde kasıtlı olarak genel olmalıdır.

Sinyalleri iptal et ve getir

Getirme işlemi AbortSignal alabilir. Örneğin, 5 saniye sonra getirme zaman aşımını şu şekilde saniye:

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 isteği hem de yanıtı iptal eder. Dolayısıyla, yanıt gövdesinin tüm okumaları da (response.text() gibi) da iptal edildi.

Bu demo – Yazıldığı sırada tek tarayıcı Firefox 57'yi destekler. Ayrıca, arkanıza yaslanın. Tasarım becerisi olan kimse katılmadı. demoyu göz önünde bulundurun.

Alternatif olarak, sinyal bir istek nesnesine verilebilir ve daha sonra getirme işlemi için iletilebilir:

const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });

fetch(request);

request.signal bir AbortSignal olduğundan bu yöntem işe yarar.

İptal edilen bir getirme işlemine tepki verme

Eş zamansız bir işlemi iptal ettiğinizde, taahhüt 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ı işlemi iptal ettiyse hata mesajının gösterilmesini genellikle istemezsiniz, çünkü bu bir "hata" kullanıcının isteğini başarıyla yerine getirirseniz. Bunu önlemek için bir örneği inceleyin.

Kullanıcıya içerik yüklemesi için bir düğme ve iptal etmek için bir düğme sağlayan bir örneği burada görebilirsiniz. Getirme iptal etme hatası olmadığı sürece 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;
});
.

Burada bir demo verilmiştir – Yazmaya başladığınızda yalnızca Edge 16 ve Firefox 57 desteklenir.

Tek sinyal, birçok getirme

Birçok getirmeyi tek seferde iptal etmek için tek bir sinyal 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 için aynı sinyal kullanılmaktadır. getirir. fetchStory aracını şu şekilde kullanırsınız:

const controller = new AbortController();
const signal = controller.signal;

fetchStory({ signal }).then(chapters => {
    console.log(chapters);
});

Bu durumda, controller.abort() işlevinin çağrılması devam eden getirme işlemlerini iptal eder.

Gelecek

Diğer tarayıcılar

Edge bu modeli ilk göndermek için harika bir iş çıkardı ve Firefox bu konuda son derece başarılı. Mühendisleri test paketinden uygulanmıştır. yazmaktan ibaret değildir. Diğer tarayıcılar için ise izlemeleri gereken biletler şunlardır:

Service Worker'da

Servis çalışanı parçaları için spesifikasyonları tamamlamam gerekiyor ancak planı şu şekilde:

Daha önce de belirttiğim gibi, her Request nesnesinin bir signal özelliği vardır. Service Worker içinde, fetchEvent.request.signal, sayfa artık yanıtla ilgilenmiyorsa iptal sinyalini verir. Sonuç olarak, şuna benzer bir kod işe yarar:

addEventListener('fetch', event => {
    event.respondWith(fetch(event.request));
});

Sayfa getirmeyi iptal ederse fetchEvent.request.signal sinyali iptal eder. Dolayısıyla, hizmet çalışanı tarafından da iptal edilir.

event.request dışında bir şey getiriyorsanız sinyali özel getirmeler olduğundan emin olun.

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 takip etmek için spesifikasyonları uygulayın. Talimatlara tarayıcı biletleri uygulanır.

Tarih

Evet... Bu nispeten basit API'nin bir araya gelmesi çok 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 mesaj dizisinde çok sayıda nüans vardır (ve herhangi bir nüans yoktur) ama temel anlaşmazlık şudur: grubu, abort yönteminin fetch() tarafından döndürülen nesnede mevcut olmasını isterken diğer tepkiyi almakla etkilemek arasında bir ayrım olmasını talep ediyordu.

Bu gereksinimler uyumsuz olduğundan bir grup istediği elde edemiyordu. Eğer özür dileriz! Daha iyi hissetmeni sağlıyorsa ben de o grupta yer aldım. Ancak AbortSignal bu durum doğru bir seçim gibi görünmesine neden oluyor. Ayrıca, zincirleme vaatlere izin vermek imkansız değilse çok karmaşık hale gelebilir.

Yanıt sağlayan ancak iptal edebilen bir nesne döndürmek istiyorsanız bir nesne oluşturabilirsiniz. basit sarmalayıcı:

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şlıyor

İptal edilmiş bir işlemi hatadan ayırt etmek için çaba gösterilmesi. Buna üçüncü bir söz, "iptal edildi"yi belirtmek için durum ve hem senkronizasyon hem de eşzamansız iptal işlemleri için bazı yeni söz dizimi kod:

Yapılmaması gerekenler:

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ılacak en yaygın işlem hiçbir şey yapmamaktır. Yukarıdaki teklif ayrıldı sayesinde, özellikle iptal hatalarını ele almanız gerekmiyor. catch cancel izin ancak çoğu zaman buna ihtiyaç duymazsınız.

TC39'da bu konuda 1. aşamaya geçmiş ancak fikir birliği sağlanamadı ve teklif geri çekildi.

Alternatif teklifimiz (AbortController) yeni bir söz dizimi gerektirmediğinden anlamlı değildi bunu TC39 kapsamında denetliyoruz. JavaScript'ten ihtiyacımız olan her şey zaten mevcuttu. Bu nedenle, DOM standardı olmak üzere, web platformundaki arayüzler için de geçerlidir. Bu kararı verdikten sonra diğerleri görece hızlı bir şekilde bir araya geldi.

Büyük özellik değişikliği

XMLHttpRequest yıllardır iptal edilmişti ancak spesifikasyon son derece belirsizdi. Şu saatte açık değildi: temel ağ etkinliğinden kaçınabileceğinizi veya sonlandırılabileceğini ya da abort() çağrılması ve getirme işleminin tamamlanması arasında bir yarış koşulu vardı.

Bu defa doğru yapmak istiyorduk. Ancak bu da büyük bir özellik değişikliğiyle sonuçlandı ve çok fazla (bu benim hatam. Anne van Kesteren ve ekibim sayesinde Domenic Denicola) ve yeterli bir test grubu.

Ama artık buradayız! Eş zamansız işlemleri iptal etmek için yeni bir temel web bileşenimiz var ve birden fazla getirme işlemi birden fazla aynı anda kontrol edilebilir! İlerleyen zamanlarda, bir getirme sürecinin ömrü boyunca öncelik değişikliklerinin nasıl etkinleştirileceğini ve daha yüksek bir düzeydeki Getirme işleminin ilerleme durumunu gözlemlemek için API.