फ़ेच करने की अनुमति रद्द की जा सकती है

"फ़ेच को रोकने" से जुड़ी मूल GitHub समस्या को 2015 में खोला गया था. अब, अगर 2017 (मौजूदा साल) से 2015 को घटाया जाए, तो मुझे 2 साल मिलेंगे. इससे गणित में मौजूद गड़बड़ी का पता चलता है, क्योंकि 2015 असल में "बहुत पहले" हुआ था.

हमने 2015 में, फ़ेच को बीच में रोकने की सुविधा पर काम करना शुरू किया था. GitHub पर 780 टिप्पणियां मिलने, कुछ बार गड़बड़ी होने, और पांच बार कोड को पुल करने के अनुरोध मिलने के बाद, हमने फ़ाइनल तौर पर ब्राउज़र में फ़ेच को बीच में रोकने की सुविधा लॉन्च की है. यह सुविधा सबसे पहले Firefox 57 में उपलब्ध होगी.

अपडेट: नहीं, मुझसे गलती हो गई. Edge 16 में, प्रोसेस को बीच में रोकने की सुविधा पहली बार उपलब्ध हुई है! Edge टीम को बधाई!

हम इतिहास के बारे में बाद में बात करेंगे, लेकिन पहले एपीआई के बारे में जानें:

कंट्रोलर + सिग्नल मैन्युवर

AbortController और AbortSignal की ज़रूरी शर्तें पूरी करें:

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

कंट्रोलर का सिर्फ़ एक तरीका है:

controller.abort();

ऐसा करने पर, यह सिग्नल को सूचना देता है:

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

यह एपीआई, DOM स्टैंडर्ड से मिलता है. यह पूरा एपीआई है. इसे जान-बूझकर सामान्य बनाया गया है, ताकि इसका इस्तेमाल अन्य वेब स्टैंडर्ड और JavaScript लाइब्रेरी कर सकें.

सिग्नल रोकना और फ़ेच करना

फ़ेच करने में AbortSignal लग सकता है. उदाहरण के लिए, यहां बताया गया है कि 5 सेकंड के बाद फ़ेच टाइम आउट कैसे सेट किया जाता है:

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);
});

फ़ेच को बंद करने पर, अनुरोध और जवाब, दोनों बंद हो जाते हैं. इसलिए, जवाब के मुख्य हिस्से (जैसे, response.text()) को पढ़ने की प्रोसेस भी बंद हो जाती है.

यहां इसका डेमो दिया गया है – फ़िलहाल, Firefox 57 ही ऐसा ब्राउज़र है जो इस सुविधा के साथ काम करता है. साथ ही, ध्यान रखें कि डेमो बनाने में किसी डिज़ाइनर की मदद नहीं ली गई है.

इसके अलावा, सिग्नल को किसी अनुरोध ऑब्जेक्ट को दिया जा सकता है और बाद में फ़ेच करने के लिए पास किया जा सकता है:

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

fetch(request);

यह इसलिए काम करता है, क्योंकि request.signal एक AbortSignal है.

फ़ेच करने की प्रोसेस को बीच में रोकने पर प्रतिक्रिया देना

किसी असाइनॉन्स (एक साथ कई काम करने की सुविधा) वाले ऑपरेशन को रोकने पर, प्रॉमिस AbortError नाम के DOMException के साथ अस्वीकार हो जाता है:

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);
    }
});

आम तौर पर, अगर उपयोगकर्ता ने कार्रवाई को रोक दिया है, तो गड़बड़ी का मैसेज नहीं दिखाया जाता. ऐसा इसलिए, क्योंकि अगर उपयोगकर्ता ने जो कहा है उसे पूरा कर दिया जाता है, तो इसे "गड़बड़ी" नहीं माना जाता. इससे बचने के लिए, ऊपर दिए गए जैसे if-स्टेटमेंट का इस्तेमाल करके, खास तौर पर, प्रोसेस को बीच में रोकने से जुड़ी गड़बड़ियों को मैनेज करें.

यहां एक उदाहरण दिया गया है, जिसमें उपयोगकर्ता को कॉन्टेंट लोड करने के लिए एक बटन और प्रोसेस को रोकने के लिए एक बटन दिया गया है. अगर फ़ेच करने में कोई गड़बड़ी होती है, तो गड़बड़ी का मैसेज दिखता है. हालांकि, अगर गड़बड़ी की वजह से फ़ेचिंग की प्रोसेस बीच में रुक जाती है, तो गड़बड़ी का मैसेज नहीं दिखता:

// 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;
});

यहां एक डेमो दिया गया है – फ़िलहाल, सिर्फ़ Edge 16 और Firefox 57 में यह सुविधा काम करती है.

एक सिग्नल, कई फ़ेच

एक सिग्नल का इस्तेमाल, एक साथ कई फ़ेच को रोकने के लिए किया जा सकता है:

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);
}

ऊपर दिए गए उदाहरण में, शुरुआती फ़ेच और पैरलल चैप्टर फ़ेच के लिए एक ही सिग्नल का इस्तेमाल किया गया है. fetchStory का इस्तेमाल करने का तरीका यहां बताया गया है:

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

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

इस मामले में, controller.abort() को कॉल करने से, फ़ेच करने की सभी प्रोसेस बंद हो जाएंगी.

आने वाला समय

अन्य ब्राउज़र

Edge ने इसे सबसे पहले लॉन्च करके बहुत अच्छा काम किया है. फ़ायरफ़ॉक्स भी इस सुविधा को जल्द ही लॉन्च कर सकता है. उनके इंजीनियरों ने स्पेसिफ़िकेशन लिखते समय, टेस्ट सुइट से इसे लागू किया. अन्य ब्राउज़र के लिए, यहां दिए गए टिकट का पालन करें:

सर्विस वर्कर में

मुझे सेवा वर्कर के हिस्सों के लिए स्पेसिफ़िकेशन पूरा करना है. हालांकि, यहां प्लान दिया गया है:

जैसा कि मैंने पहले बताया था, हर Request ऑब्जेक्ट में एक signal प्रॉपर्टी होती है. अगर पेज को रिस्पॉन्स में कोई दिलचस्पी नहीं है, तो सेवा वर्कर में fetchEvent.request.signal, 'रद्द करें' सिग्नल देगा. इस वजह से, इस तरह का कोड काम करता है:

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

अगर पेज फ़ेच को रोकता है, तो fetchEvent.request.signal सिग्नल भी रुक जाते हैं. इसलिए, सेवा वर्कर में फ़ेच भी रुक जाता है.

अगर event.request के अलावा किसी और चीज़ को फ़ेच किया जा रहा है, तो आपको सिग्नल को अपने कस्टम फ़ेच(फ़ेच) पर भेजना होगा.

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 })
    );
    }
});

इसे ट्रैक करने के लिए, स्पेसिफ़िकेशन का पालन करें – लागू होने के बाद, मैं ब्राउज़र टिकट में लिंक जोड़ दूंगी.

इतिहास

हां… इस आसान एपीआई को तैयार करने में काफ़ी समय लगा. यहां इसकी वजह दी गई है:

एपीआई से जुड़ी गड़बड़ी

जैसा कि आपको दिख रहा है, GitHub पर हुई बातचीत काफ़ी लंबी है. उस थ्रेड में बहुत सारी बारीकियां हैं (और कुछ बारीकियां नहीं हैं), लेकिन मुख्य असहमति यह है कि एक ग्रुप चाहता था कि abort का तरीका, fetch() से वापस किए गए ऑब्जेक्ट पर मौजूद हो, जबकि दूसरे ग्रुप को जवाब पाने और जवाब पर असर डालने के बीच अलगाव चाहिए था.

ये शर्तें एक-दूसरे के साथ काम नहीं करतीं. इसलिए, एक ग्रुप को वह नहीं मिल पा रहा था जो उसे चाहिए था. अगर आपने ऐसा किया है, तो माफ़ करें! अगर आपको इससे अच्छा महसूस होता है, तो आपको बता दें कि मैं भी उस ग्रुप में शामिल था. हालांकि, AbortSignal को अन्य एपीआई की ज़रूरी शर्तों के मुताबिक देखकर, यह सही विकल्प लगता है. साथ ही, चेन किए गए प्रॉमिस को बीच में रोकने की अनुमति देना बहुत मुश्किल होगा.

अगर आपको कोई ऐसा ऑब्जेक्ट दिखाना है जो जवाब देता है, लेकिन उसे बीच में रोका भी जा सकता है, तो एक आसान रैपर बनाया जा सकता है:

function abortableFetch(request, opts) {
    const controller = new AbortController();
    const signal = controller.signal;

    return {
    abort: () => controller.abort(),
    ready: fetch(request, { ...opts, signal })
    };
}

TC39 में गलत शुरुआत

रद्द की गई कार्रवाई को गड़बड़ी से अलग करने की कोशिश की गई थी. इसमें "रद्द किया गया" दिखाने के लिए, तीसरी प्रॉमिस वाली स्थिति शामिल की गई है. साथ ही, सिंक और असाइन किए गए कोड, दोनों में रद्द करने की प्रोसेस को मैनेज करने के लिए, कुछ नया सिंटैक्स भी शामिल किया गया है:

यह न करें

कोड असली नहीं है — प्रस्ताव वापस ले लिया गया था

    try {
      // Start spinner, then:
      await someAction();
    }
    catch cancel (reason) {
      // Maybe do nothing?
    }
    catch (err) {
      // Show error message
    }
    finally {
      // Stop spinner
    }

आम तौर पर, कोई कार्रवाई रद्द होने पर कुछ नहीं किया जाता. ऊपर दिए गए प्रस्ताव में, रद्द करने की प्रोसेस को गड़बड़ियों से अलग रखा गया है, ताकि आपको खास तौर पर, रद्द करने की गड़बड़ियों को मैनेज करने की ज़रूरत न पड़े. catch cancel की मदद से, आपको रद्द की गई कार्रवाइयों के बारे में पता चलता है. हालांकि, ज़्यादातर मामलों में ऐसा ज़रूरी नहीं होता.

यह TC39 में पहले चरण तक पहुंचा, लेकिन सहमति नहीं बनी और प्रस्ताव वापस ले लिया गया.

हमारे दूसरे प्रस्ताव, AbortController के लिए किसी नए सिंटैक्स की ज़रूरत नहीं थी. इसलिए, TC39 में इसके बारे में बताने का कोई मतलब नहीं था. JavaScript में हमें जो कुछ भी चाहिए था वह पहले से ही मौजूद था. इसलिए, हमने वेब प्लैटफ़ॉर्म में इंटरफ़ेस तय किए, खास तौर पर DOM स्टैंडर्ड. यह फ़ैसला लेने के बाद, बाकी काम आसानी से हो गया.

स्पेसिफ़िकेशन में बड़ा बदलाव

XMLHttpRequest को कई सालों से बंद किया जा सकता है, लेकिन इसके लिए दी गई जानकारी बहुत ही अस्पष्ट थी. यह साफ़ तौर पर नहीं पता था कि नेटवर्क गतिविधि को किन पॉइंट पर रोका या बंद किया जा सकता है. इसके अलावा, यह भी नहीं पता था कि abort() को कॉल करने और फ़ेच पूरा होने के बीच रेस कंडीशन होने पर क्या होगा.

हम इस बार इसे सही तरीके से लागू करना चाहते थे. हालांकि, इस वजह से स्पेसिफ़िकेशन में काफ़ी बदलाव हुए. इन बदलावों की समीक्षा करने में काफ़ी समय लगा. इसकी ज़िम्मेदारी मेरी है. साथ ही, ऐनी वैन केस्टरिन और डोमेनिक डेनिकोला को धन्यवाद, जिन्होंने मुझे इस काम में मदद की. साथ ही, जांच के लिए अच्छे सेट की ज़रूरत पड़ी.

हालांकि, अब हम यहां हैं! हमारे पास असाइन किए गए क्रम के मुताबिक नहीं होने वाली कार्रवाइयों को रोकने के लिए, एक नया वेब प्राइमिटिव है. साथ ही, एक साथ कई फ़ेच को कंट्रोल किया जा सकता है! आने वाले समय में, हम फ़ेच के पूरे समय के दौरान प्राथमिकता में बदलाव करने की सुविधा चालू करेंगे. साथ ही, फ़ेच की प्रोग्रेस को देखने के लिए, एक बेहतर लेवल का एपीआई भी उपलब्ध कराएंगे.