乐趣区

fetch使用的常见问题及其解决办法

首先声明一下,本文不是要讲解 fetch 的具体用法,不清楚的可以参考 MDN fetch 教程。

fetch 默认不携带 cookie

配置其 credentials 项,其有 3 个值:

  • omit: 默认值,忽略 cookie 的发送
  • same-origin: 表示 cookie 只能同域发送,不能跨域发送
  • include: cookie 既可以同域发送,也可以跨域发送

credentials 所表达的含义,其实与 XHR2 中的 withCredentials 属性类似,表示请求是否携带 cookie

这样,若要 fetch 请求携带 cookie 信息,只需设置一下 credentials 选项即可,例如 fetch(url, {credentials: ‘include’});

fetch 请求对某些错误 http 状态不会 reject

这主要是由 fetch 返回 promise 导致的,因为 fetch 返回的 promise 在某些错误的 http 状态下如 400、500 等不会 reject,相反它会被 resolve;只有网络错误会导致请求不能完成时,fetch 才会被 reject;所以一般会对 fetch 请求做一层封装,例如下面代码所示:

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

fetch 不支持超时 timeout 处理

方法一:单纯 setTimeout 方式

var oldFetchfn = fetch; // 拦截原始的 fetch 方法
window.fetch = function(input, opts){// 定义新的 fetch 方法,封装原有的 fetch 方法
    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)
            }
        )
    })
}

当然在上面基础上可以模拟类似 XHR 的 abort 功能:

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

方法二:利用 Promise.race 方法
Promise.race 方法接受一个 promise 实例数组参数,表示多个 promise 实例中任何一个最先改变状态,那么 race 方法返回的 promise 实例状态就跟着改变,具体可以参考这里。

var oldFetchfn = fetch; // 拦截原始的 fetch 方法
window.fetch = function(input, opts){// 定义新的 fetch 方法,封装原有的 fetch 方法
    var fetchPromise = oldFetchfn(input, opts);
    var timeoutPromise = new Promise(function(resolve, reject){setTimeout(()=>{reject(new Error("fetch timeout"))
        }, opts.timeout)
    });
    retrun Promise.race([fetchPromise, timeoutPromise])
}

fetch 不支持 JSONP

npm install fetch-jsonp --save-dev

然后在像下面一样使用:

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

fetch 不支持 progress 事件

XHR 是原生支持 progress 事件的,例如下面代码这样:

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; // 上传的 progress 事件
xhr.onprogress = updateProgress; // 下载的 progress 事件
}
xhr.send();

但是 fetch 是不支持有关 progress 事件的;不过可喜的是,根据 fetch 的指导规范标准,其内部设计实现了 Request 和 Response 类;其中 Response 封装一些方法和属性,通过 Response 实例可以访问这些方法和属性,例如 response.json()、response.body 等等;

值得关注的地方是,response.body 是一个可读字节流对象,其实现了一个 getRender() 方法,其具体作用是:

getRender() 方法用于读取响应的原始字节流,该字节流是可以循环读取的,直至 body 内容传输完成;

因此,利用到这点可以模拟出 fetch 的 progress

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

fetch 跨域问题

fetch 的 mode 配置项有 3 个值,如下:

  • same-origin:该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个 error 告知不能跨域;其对应的 response type 为 basic。
  • cors: 该模式支持跨域请求,顾名思义它是以 CORS 的形式跨域;当然该模式也可以同域请求不需要后端额外的 CORS 支持;其对应的 response type 为 cors。
  • no-cors: 该模式用于跨域请求但是服务器不带 CORS 响应头,也就是服务端不支持 CORS;这也是 fetch 的特殊跨域请求方式;其对应的 response type 为 opaque。

参考

  • MDN-ReadableStream
  • MDN fetch
  • https://segmentfault.com/a/1190000008484070#articleHeader3

友情链接

  • 前端知识体系
  • react 服务端渲染博客
退出移动版