Play fetch.

  • Author: wonyun
  • Common problems used by FETCH and their solutions

FundebugReproduced with authorization, copyright belongs to the original author.

First of all, this article is not intended to explain the exact use of FETCH. If not, please refer to the MDN Fetch tutorial.

The introduction

When it comes to FETCH, XMLHttpRequest has to be mentioned. When XHR sends web requests, developers need to configure relevant request information and callback after success. Although developers only care about business processing after success, they also need to configure other tedious content, which leads to confusion in configuration and invocation and does not conform to the principle of separation of concerns. Fetch emerged to address these issues with XHR. For example:

fetch(url)
    .then(function(response) {
        return response.json();
    })
    .then(function(data) {
        console.log(data);
    })
    .catch(function(e) {
        console.log("Oops, error");
    });
Copy the code

The above code lets the developer focus only on the business logic after the request is successful. It’s also more in line with the modern Promise form, which is friendlier.

Fetch is designed based on Promise, as you can see from the code above, which requires fetch to be used in conjunction with Promise. It is this design that brings the same benefits to FETCH as traditional Ajax is dead and FETCH is immortal:

  • Simple syntax, more semantic
  • Standards-based Promise implementation that supports async/await
  • Isomorphic-fetch can facilitate isomorphism

However, fetch has many advantages, but there are also some common problems when using FETCH for project development. Here are some common problems with the use of FETCH.

The fetch compatibility

Fetch is a relatively new technology, and of course there will be browser compatibility issues. To illustrate the native support of FETCH in various browsers, use a picture from the application article above:

As you can see from the figure above, this is not supported in any of the older versions of the browser.

The question then arises, how to generalize fetch across all browsers, and of course consider the polyfill of fetch.

As mentioned above, fetch is implemented based on promises, so promises may not be supported natively in older browsers, so Promise polyfills are required. In most cases, implementing a fetch polyfill involves:

  • Polyfills of promises, such as es6-Promise and babel-Polyfill provide promise implementations.
  • Polyfill implementations of fetch, such as Isomorphic-fetch and WHATWG-fetch

Is it safe to use fetch for back-end communication? This is true in most cases, but IE8/9 is special: IE8 uses ES3, while IE9 partially supports ES5. In this case, ES5 polyfill ES5-SHIm support is also needed.

The above polyfill implementation of promise needs to be explained as follows:

The babel-Runtime cannot be used as a polyfill implementation of the Promise, otherwise using fetch under IE8/9 would declare the Promise undefined. Why is that? Babel-runtime polyfill is a local implementation, not a global implementation. Fetch’s underlying implementation uses Promise from the global implementation.

In addition, by the way, the idea of polyfill implementation of FETCH is as follows:

First, determine whether the browser supports fetch natively, otherwise use XMLHttpRequest with Promise. This is exactly the implementation idea of WHATWG-FETCH. For isomorphic-FETCH used in isomorphic applications, its client FETCH is directly implemented by requiring WHATWG-FETCH.

Fetch does not carry cookies by default

The fetch sends requests without cookies by default, either in the same domain or across domains; In this case, the credentials entry can be configured with three values:

  • omit: Default value, ignoring the sending of cookies
  • same-origin: Indicates that cookies can be sent only in the same domain but not across domains
  • include: Cookies can be sent either in the same domain or across domains

The credentials attribute is similar to the withCredentials attribute in XHR2. It indicates whether the request carries cookies. For details, please refer to the withCredentials section in Teacher Ruan Yifeng’s detailed explanation of cross-domain resource sharing CORS.

Thus, to fetch requests with cookie information, you only need to set the credentials option, for example, FETCH (URL, {credentials: ‘include’});

One more thing:

By default, fetch ignores cookies Set through the set-cookie header on the server. To accept cookies from the server, you must configure the credentials option.

The FETCH request does not reject certain incorrect HTTP states

This is mainly caused by the fact that fetch returns a promise, because fetch does not reject promises in some incorrect HTTP states such as 400, 500, etc. Instead, it will be resolved. A fetch is rejected only if a network error causes the request to be rejected; So the fetch request is generally wrapped in a layer, as shown in the following code:

function checkStatus(response) {
    if (response.status >= 200 && response.status < 300) {
        return response;
    }
    const error = new Error(response.statusText);
    error.response = response;
    throw error;
}

function parseJSON(response) {
    return response.json();
}

export default function request(url, options) {
    let opt = options || {};
    return fetch(url, { credentials: "include". opt }) .then(checkStatus) .then(parseJSON) .then(data= > data)
        .catch(err= > err);
}
Copy the code

Fetch does not support timeout processing

As anyone who has used FETCH knows, fetch does not set timeout for requests like most Ajax libraries do, and it has no feature about request timeout, which is a pain in the neck. Therefore, polyfill is required before the fetch standard adds the timeout feature.

In fact, what we really need is abort(). Timeout can be done with timeout+abort to actually timeout out the current request.

In the current FETCH guidance specification, fetch is not an instance, but a method; The returned promise instance cannot be abort according to the promise guidance specification standard, nor can the state of the promise instance be manually changed, but the state of the promise can only be changed internally according to the request result.

Since you can’t manually control the state of the Promise instance returned after the FETCH method is executed, isn’t it possible to create a new Promise instance that can manually control the state? So:

To realize the timeout function of FETCH, the idea is to create a new instance that can manually control the promise state, and perform resolve or reject on the new promise instance according to different situations, so as to achieve the function of timeout.

According to the discussion on Timeout Handling on Github, there are currently two different solutions:

Method 1: Simple setTimeout mode

var oldFetchfn = fetch; // Intercept the original fetch method
window.fetch = function(input, opts) {
    // Define a new fetch method that encapsulates the old fetch method
    return new Promise(function(resolve, reject) {
        var timeoutId = setTimeout(function() {
            reject(new Error("fetch timeout"));
        }, opts.timeout);
        oldFetchfn(input, opts).then(
            res= >{ clearTimeout(timeoutId); resolve(res); }, err => { clearTimeout(timeoutId); reject(err); }); }); };Copy the code

Of course, xHR-like abort can be simulated from above:

var oldFetchfn = fetch;
window.fetch = function(input, opts) {
    return new Promise(function(resolve, reject) {
        var abort_promise = function() {
            reject(new Error("fetch abort"));
        };
        var p = oldFetchfn(input, opts).then(resolve, reject);
        p.abort = abort_promise;
        return p;
    });
};
Copy the code

Method two: Use the promise.race method

The promise. race method takes an array of Promise instances, indicating that if any of the multiple Promise instances changes state first, the state of the Promise instance returned by the RACE method will change, as shown here.

var oldFetchfn = fetch; // Intercept the original fetch method
window.fetch = function(input, opts){// Define a new fetch method that encapsulates the old fetch method
    var fetchPromise = oldFetchfn(input, opts);
    var timeoutPromise = new Promise(function(resolve, reject){
        setTimeout((a)= >{
             reject(new Error("fetch timeout"))
        }, opts.timeout)
    });
    retrun Promise.race([fetchPromise, timeoutPromise])
}
Copy the code

Finally, add a few points to the above implementation of timeout of fetch:

Timeout is not the meaning of connection timeout. It refers to the response time of a request, including the time of connection, server processing and server response.

Fetch timeout even if the timeout occurs, the fetch request is not abort. It is still sent to the server in the background, but the response content is discarded.

Fetch does not support JSONP

Fetch interacts asynchronously with the server, while JSONP is a javascript resource that is externally linked, which is not real Ajax, so FETCH has no direct connection with JSONP. Of course, JSONP is not supported at least for now.

The association of JSONP with fetch here is somewhat inadequate. Fetch is just an Ajax library, and there is no way that fetch can support JSONP; It’s just that we need to implement a JSONP, but the implementation of the JSONP is similar to the implementation of fetch, that is, implement a JSONP based on Promise; The external appearance of fetch looks like it supports JSONP.

Fetch – JSONP, a mature open source JSONP implementation, provides us with a solution. If you want to know more about it, you can go there by yourself. But again, I want to talk about the implementation steps of JSONP, because most of the front-end candidates I interviewed didn’t know anything about the implementation of JSONP.

It is very simple to use, and you first need to install fetch-jsonp with NPM

npm install fetch-jsonp --save-dev
Copy the code

Then use it as follows:

fetchJsonp("/users.jsonp", {
    timeout: 3000.jsonpCallback: "custom_callback"
})
    .then(function(response) {
        return response.json();
    })
    .catch(function(ex) {
        console.log("parsing failed", ex);
    });
Copy the code

Fetch does not support progress events

XHR natively supports progress events, such as the following code:

var xhr = new XMLHttpRequest();
xhr.open("POST"."/uploads");
xhr.onload = function() {};
xhr.onerror = function() {};
function updateProgress(event) {
    if (event.lengthComputable) {
        var percent = Math.round((event.loaded / event.total) * 100);
        console.log(percent);
    }
    xhr.upload.onprogress = updateProgress; // Upload progress event
    xhr.onprogress = updateProgress; // Download the progress event
}
xhr.send();
Copy the code

Fetch, however, does not support progress events; The good news is that fetch’s internal design implements Request and Response classes according to its guiding specification standards; Response encapsulates some methods and properties that can be accessed through Response instances, such as Response.json (), response.body, etc.

Response.body is a readable byte stream object that implements a getRender() method.

The getRender() method is used to read the original byte stream of the response, which can be read in a loop until the body content has been transferred.

Therefore, the progress of FETCH can be simulated by taking advantage of this. For details, please refer to this article 2016 – The Year of Web Streams.

Code implementation is as follows, online demo please refer to fetch Progress demo.

// fetch() returns a promise that resolves once headers have been received
fetch(url).then(response= > {
    // response.body is a readable stream.
    // Calling getReader() gives us exclusive access to the stream's content
    var reader = response.body.getReader();
    var bytesReceived = 0;

    // read() returns a promise that resolves when a value has been received
    reader.read().then(function processResult(result) {
        // Result objects contain two properties:
        // done - true if the stream has already given you all its data.
        // value - some data. Always undefined when done is true.
        if (result.done) {
            console.log("Fetch complete");
            return;
        }

        // result.value for fetch streams is a Uint8Array
        bytesReceived += result.value.length;
        console.log("Received", bytesReceived, "bytes of data so far");

        // Read some more, and call this function again
        return reader.read().then(processResult);
    });
});
Copy the code

In addition, github also uses Promise+XHR to achieve the progress effect of class FETCH (of course, this is completely unrelated to fetch). The specific code is as follows:

function fetchProgress(url, opts={}, onProgress){
    return new Promise(funciton(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.open(opts.method || 'get', url);
        for(var key in opts.headers || {}){
            xhr.setRequestHeader(key, opts.headers[key]);
        }

        xhr.onload = e= > resolve(e.target.responseText)
        xhr.onerror = reject;
        if (xhr.upload && onProgress){
            xhr.upload.onprogress = onProgress; / / upload
        }
        if ('onprogerss' in xhr && onProgress){
            xhr.onprogress = onProgress; / / download
        }
        xhr.send(opts.body)
    })
}

fetchProgress('/upload').then(console.log)
Copy the code

Fetch cross-domain problem

Since it is an Ajax library, it is unavoidable to have cross-domain relationships; XHR2 supports cross-domain requests, but only if the browser supports CORS and the server allows the specified source to cross domains through access-Control-Allow-Origin.

Fetch supports cross-domain requests just like XHR2, except that it requires client and server support for cross-domain requests. Fetch also supports a cross-domain form that does not require server support, as illustrated by its mode configuration item.

The fetch mode configuration item has three values, as follows:

  • same-origin: This mode is not allowed to cross domains. It must comply with the same origin policy, otherwise the browser will return an error telling you that it cannot cross domains. The corresponding response type isbasic.
  • cors: This pattern supports cross-domain requests, which, as the name implies, cross domains in the form of CORS; Of course, this mode can also be same-domain requests without the need for additional BACKEND CORS support; The corresponding response type iscors.
  • no-cors: This mode is used for cross-domain requests but the server does not have a CORS response header, that is, the server does not support CORS; This is also the special cross-domain request approach of FETCH; The corresponding response type isopaque.

For cross-domain request, CORS mode is a common implementation of cross-domain request, but fetch’s no-CORS cross-domain request mode is relatively unknown. This mode has an obvious feature:

This mode allows the browser to send the cross-domain request but cannot access the content returned in the response. This is why the response Type is opaque and transparent.

This is similar to the request sent by , except that the mode does not access the content information of the response; But it can be handled by other APIs, such as ServiceWorker. In addition, the repsonse returned by this pattern can be stored in the Cache API for subsequent use, which is appropriate for SCRIPT, CSS, and image CDN resources, which do not have CORS headers in their response headers.

In general, fetch’s cross-domain request uses CORS and requires browser and server support.

reference

  • thats-so-fetch
  • Traditional Ajax is dead, Fetch lives forever
  • Fetch advanced guide
  • 2016 – the year of web streams
  • Introduction of the fetch API

About Fundebug

Fundebug focuses on real-time BUG monitoring for JavaScript, wechat applets, wechat games, Alipay applets, React Native, Node.js and Java online applications. Since its official launch on November 11, 2016, Fundebug has handled over 1 billion error events in total, and paid customers include Sunshine Insurance, Walnut Programming, Lychee FM, Zhangmen 1-to-1, Weimai, Qingtuanshe and many other brand enterprises. Welcome to try it for free!