fetch-event-source(fetch)

3,390 阅读2分钟

fetch-event-source (fetch)分析: 文件里导出的有EventStreamContentType/FetchEventSourceInit/fetchEventSource,前面两个分别是contentType和ts声明,不是主要的,我们主要是需要了解fetchEventSource方法

fetchEventSource是什么

function fetchEventSource(input: RequestInfo, {
    signal: inputSignal,
    headers: inputHeaders,
    onopen: inputOnOpen,
    onmessage,
    onclose,
    onerror,
    openWhenHidden,
    fetch: inputFetch,
    ...rest
}: FetchEventSourceInit) {
    return new Promise((resolve, reject)=> {
        async create() {
            fetch(url, {...other})
        }
        create()
    })
}

将fetchEventSource方法提炼如上所示,上面代码可以看出fetchEventSource主要是根据传入参数进行逻辑组装发起请求,返回promise对象。

  • signal: 见下面疑惑分析
  • headers: 主要处理请求头,同时设置请求的accept类型为'text/event-stream'
const headers = { ...inputHeaders };
if (!headers.accept) {
    headers.accept = EventStreamContentType;
}
fetch(url, {headers, ...other})
  • onopen: 当接口请求正常返回会执行该方法
const response = await fetch(input, {
    ...rest,
    headers,
    signal: curRequestController.signal,
});

await onopen(response);
  • onmessage:在parse中学习getBytes/getLines方法后才会知道onMessage做什么用,此时只知道接口成功返回后执行
const response = await fetch(input, {
    ...rest,
    headers,
    signal: curRequestController.signal,
});

await onopen(response);
await getBytes(response.body!, getLines(getMessages(id => {
    if (id) {
        // store the id and send it back on the next retry:
        headers[LastEventId] = id;
    } else {
        // don't send the last-event-id header anymore:
        delete headers[LastEventId];
    }
}, retry => {
    retryInterval = retry;
}, onmessage)));
  • onclose: 也是接口返回成功执行
  • onerror: 接口返回失败执行
try {
const response = await fetch(input, {
    ...rest,
    headers,
    signal: curRequestController.signal,
});
} catch(e => {
    onerror(e)
})
  • openWhenHidden: 窗口切换关闭和开启请求
function onVisibilityChange() {
    curRequestController.abort(); // close existing request on every visibility change
    if (!document.hidden) {
        create(); // page is now visible again, recreate request.
    }
}

    if (!openWhenHidden) {
        document.addEventListener('visibilitychange', onVisibilityChange);
    }
}
  • fetch: 请求后端接口的方法,如果不传,默认是window.fetch;否则采用传入的方法
  • rest: 接口请求的其它参数
 const response = await fetch(input, {
    ...rest,
    headers,
    signal: curRequestController.signal,
});

通过分析传入的参数,会有几个疑惑,第一signal是什么;第二onOpen/onMessage/onClose都是接口请求完执行,这两个分别处理什么。

疑惑分析

signal

fetch.ts文件中除了传入的signal,在fetch请求中也传了signal, 它主要是控制器对象AbortController, 用于中止一个和多个web请求。

curRequestController = new AbortController();
const response = await fetch(input, {
    ...rest,
    headers,
    signal: curRequestController.signal,
});

当inputSignal监听到中止abort方法,会移除相关方法和定时器,同时中止当前fetch请求。猜测传入的inputSignal和fetch中的signal类似,用于中止请求, 譬如chatgpt消息正在显示(打字机效果),此时点击中止按钮,执行inputSignal.abort()方法,阻止内部fetch执行

 function dispose() {
    document.removeEventListener('visibilitychange', onVisibilityChange);
    window.clearTimeout(retryTimer);
    curRequestController.abort();
}

// if the incoming signal aborts, dispose resources and resolve:
inputSignal?.addEventListener('abort', () => {
    dispose();
    resolve(); // don't waste time constructing/logging errors
});

onOpen/onClose/onMessage区别

首先执行onOpen,告诉用户fetch接口请求执行完成。为请求是text/event-stream类型,数据不是一次就获取完成,因为通过onMessage依次来获取,同时也可以实现打印机输出的效果,具体分析见另一篇文章fetch-event-source(parse),当所有的onMessage执行完成后,执行onClose方法