fetch-event-source(parse)

202 阅读2分钟

fetch-event-source (parse)分析:

文件里导出的有EventSourceMessage/getBytes/getLines/getMessages,前面一个是ts声明,主要是需要了解后面三个方法

getBytes

export async function getBytes(stream: ReadableStream<Uint8Array>, onChunk: (arr: Uint8Array) => void) {
    const reader = stream.getReader();
    let result: ReadableStreamDefaultReadResult<Uint8Array>;
    while (!(result = await reader.read()).done) {
        onChunk(result.value);
    }
}

getBytes主要接受2个参数,第一个是ReadableStream类型的stream, 第二个是一个方法。通过while循环,每次读取stream的数据并放在onChunk放中进行处理。

await getBytes(response.body!, getLines(...args));

getLines

function getLines(onLine: (line: Uint8Array, fieldLength: number) => void) {
   return function onChunk(arr: Uint8Array) {
   }
}

getLines传入一个onLine方法,并返回onChunk,所以在getBytes最后传入的getLines方法可以看出里面会含有onChunk方法

function onChunk(arr: Uint8Array) {
    if (buffer === undefined) {
        buffer = arr;
        position = 0;
        fieldLength = -1;
    } else {
        // we're still parsing the old line. Append the new bytes into buffer:
        buffer = concat(buffer, arr);
    }

    const bufLength = buffer.length;
    let lineStart = 0; // index where the current line starts
    while (position < bufLength) {
        if (discardTrailingNewline) {
            if (buffer[position] === ControlChars.NewLine) {
                lineStart = ++position; // skip to next char
            }

            discardTrailingNewline = false;
        }

        // start looking forward till the end of line:
        let lineEnd = -1; // index of the \r or \n char
        for (; position < bufLength && lineEnd === -1; ++position) {
            switch (buffer[position]) {
                case ControlChars.Colon:
                    if (fieldLength === -1) { // first colon in line
                        fieldLength = position - lineStart;
                    }
                    break;
                // @ts-ignore:7029 \r case below should fallthrough to \n:
                case ControlChars.CarriageReturn:
                    discardTrailingNewline = true;
                case ControlChars.NewLine:
                    lineEnd = position;
                    break;
            }
        }

        if (lineEnd === -1) {
            // We reached the end of the buffer but the line hasn't ended.
            // Wait for the next arr and then continue parsing:
            break;
        }

        // we've reached the line end, send it out:
        onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
        lineStart = position; // we're now on the next line
        fieldLength = -1;
    }

    if (lineStart === bufLength) {
        buffer = undefined; // we've finished reading it
    } else if (lineStart !== 0) {
        // Create a new view into buffer beginning at lineStart so we don't
        // need to copy over the previous lines when we get the new arr:
        buffer = buffer.subarray(lineStart);
        position -= lineStart;
    }
}

其中buffer/position/fieldLength/discardTrailingNewline是闭包在getBytes中的变量,通过观察onChunk方法,其实大部分都在处理buffer和长度之类的,主要会将处理某个长度范围内的子buffer用于调用方法onLine。

getBytes(response.body!, 
    getLines(
        getMessages(id => {}, retry => {retryInterval = retry; }, onmessage)
    )
)

getMessages

getMessage主要传入一个id, 一个onRetry方法和一个onMessage方法,返回一个onLine方法

getMessages(
    onId: (id: string) => void,
    onRetry: (retry: number) => void,
    onMessage?: (msg: EventSourceMessage) => void
) {
    return function onLine(line: Uint8Array, fieldLength: number) {

 }
}

这里的onMessage就是在fetchEventSource中传入的onMessage方法,当一行结束或整体结束时会把上一段的消息onMessage?.(message)暴露出去,当文字较多切换行时,onMessage会不断的执行接受message,但是呢消息是eventStream类型,不是一次拿到,因此通过onMessage每次获取一段从而可以达到打印机输出效果

function onLine(line: Uint8Array, fieldLength: number) {
    if (line.length === 0) {
        // empty line denotes end of message. Trigger the callback and start a new message:
        onMessage?.(message);
        message = newMessage();
    } else if (fieldLength > 0) { // exclude comments and lines with no values
        // line is of format "<field>:<value>" or "<field>: <value>"
        // https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
        const field = decoder.decode(line.subarray(0, fieldLength));
        const valueOffset = fieldLength + (line[fieldLength + 1] === ControlChars.Space ? 2 : 1);
        const value = decoder.decode(line.subarray(valueOffset));

        switch (field) {
            case 'data':
                // if this message already has data, append the new value to the old.
                // otherwise, just set to the new value:
                message.data = message.data
                    ? message.data + '\n' + value
                    : value; // otherwise, 
                break;
            case 'event':
                message.event = value;
                break;
            case 'id':
                onId(message.id = value);
                break;
            case 'retry':
                const retry = parseInt(value, 10);
                if (!isNaN(retry)) { // per spec, ignore non-integers
                    onRetry(message.retry = retry);
                }
                break;
        }
    }
}