如何实现分片加载网络数据?让页面秒开无白屏!

网络请求是前端开发中非常重要的一环,但是当我们处理较大的网络请求时,我们往往会面临着长时间的等待和页面白屏的问题。

这不仅影响了用户体验,也降低了网站的性能。

那么,你是如何解决这一问题呢?有没有什么好的方法可以让我们在网络请求时既快又好呢?

我是渡一前端子辰老师,今天我来为大家介绍一种实用的技巧——分片接收网络数据

这种技巧可以让我们在网络请求时不必等待整个响应体传输完成,而是分片地接收和显示数据。

这样就可以大大缩短等待时间,避免页面白屏的出现,提高用户体验。

接下来,子辰将通过一个实例来深入讲解这个实用的技巧。

我们将利用 fetch API 和 TextDecoder 解码器来实现分片接收网络数据,并将数据分片显示在页面上。

思考

首先我们来看一段非常简单的网络请求代码:

async function loadNovel() {




  const url = "https://duyi-static.oss-cn-beijing.aliyuncs.com/files/novel.txt";



  const resp = await fetch(url);




  const text = await resp.text();
  console.log(text);
}
loadNovel();

这段代码是请求服务器上的一段 txt 文件,文件大小接近10MB。

那么这么大的文件,一旦在网络不好的情况下,就会加载很久:

我们将网络限制后会发现,等待了 30 多秒才拿到服务器响应的结果。

如果说这个结果需要在页面上显示,就会导致页面上很长一段时间的白屏,而造成白屏的原因就在代码之中,我们可以看到,代码中有两个 await。

那么你有没有一个疑问:我们为什么要等待两次呢?我们来看一个图:

如图所示,响应头传输完成时 await fetch(url) 的 Promise 完成,响应体的传输完成时 await resp.text() 的 Promise 完成。

所以第一次等待的是响应头的传输,第二次等待的是响应体的传输。

那么造成白屏的原因其实就是因为响应体传输的时间太久,如果我们能够在响应体传输过程中就开始接收和显示数据呢?那岂不是更好?

分片加载

其实,在响应体传输过程中接收和显示数据是完全可行的,因为响应体是一点一点传输过来的,并不是一次性发送完毕。

如果我们可以分片地接收它传过来的数据,并及时地显示在页面上,那就可以让用户感觉到网站很快速地响应了他们的请求。

这里我们就可以利用 fetch API 中的 getReader 来分片读取。

首先我们知道 resp 对象中有一个 body,它代表的是响应体,只不过这个响应体还未完成。

通过上图我们可以看到 body 的类型是 ReadableStream,其实是一个可读流,那么我们可以利用 body 里的一个属性 body.getReader 拿到这个流的读取器。

读取器中有一个 read 属性,可以读取到数据。

知道了怎么做,我们用代码实现一下:

async function loadNovel() {




  const url = "https://duyi-static.oss-cn-beijing.aliyuncs.com/files/novel.txt";



  const resp = await fetch(url);




  // resp.body 从 resp 中拿到响应体
  // 响应体中有一个方法,叫 getReader
  // 返回的是一个 reader 对象
  // 返回的 reader 对象是用来分片读取响应体的
  const reader = resp.body.getReader();
  // read 是一个 Promise 函数,表示读取响应体目前传输的一部分,返回值是一个对象
  // 这个对象里有两个属性 value 和 done
  // 学过 ES6 的同学应该不陌生,这就是迭代器的东西
  // value 表示读取的一个小切片数据
  // done 表示是不是已经读取完成,读取完为 true,未读取完为 false
  const { value, done } = await reader.read();
  // 我们打印看一下 value 是什么
  console.log("value >>> ", value);
}
loadNovel();

打印出来可以看到,虽然我们读取的是文本,但是结果并不是一个字符串。

这是因为 reader.read() 函数是一个通用型函数,它也不知道服务器响应给你的是什么数据,因此它把数据统一成了由一个个字节组成的字节数组。

而我们现在读取的是文本,所以我们要把字节数组转化为文本,这里就要使用到一个非常有用的属性了,叫做文本解码器 TextDecoder,通过文本解码器我们就可以得到文本数据了:

async function loadNovel() {




  const url = "https://duyi-static.oss-cn-beijing.aliyuncs.com/files/novel.txt";



  const resp = await fetch(url);




  const reader = resp.body.getReader();


  const { value, done } = await reader.read();
  // 新建一个文本解码器
  const decoder = new TextDecoder();
  // 使用 decoder.decode 将读取的切片数据传入进去,就会得到文本数据
  const text = decoder.decode(value);
  // 打印一下 text,看看是否转化文本数据
  console.log("text >>> ", text);
}
loadNovel();

可以看到,我们已经将字节数组解码为文本了。

那么一个切片数据我们是这样读取的,多个切片数据我们就可以用一个循环来读取:

async function loadNovel() {




  const url = "https://duyi-static.oss-cn-beijing.aliyuncs.com/files/novel.txt";



  const resp = await fetch(url);




  const reader = resp.body.getReader();


  const decoder = new TextDecoder();

  // 为方便测试,我们声明一个变量,代表第几次读取 text 的值
  let num = 0;
  // for 无限定条件永远循环
  for (;;) {
    // 每一次循环都读取一次数据
    const { value, done } = await reader.read();
    // 如果完成了直接退出
    if (done) {
      break;
    }
    // 为完成读取文本
    const text = decoder.decode(value);
    // 输出 text
    console.log(`${num++} text >>> `, text);
  }

}
loadNovel();

可以看到,在网速调慢后,当我们使用循环分片读取文本数据时,我们共读取了 2817 个切片数据。

虽然读取数据总时长没变,但是用户却能够尽快地看到数据显示在页面上,这样就大大提高了用户体验和网站性能。

当然,在分片加载时还有一个小问题需要注意:

上图所示,由于文字编码方式不同(英文字符通常为一个字节,而中文字符通常为多个字节),可能会出现某个文字被切割成两半而导致乱码问题。

比如说有一个文字由两个字节组成。而它刚好被切割在两个切片之间:第一个字节在切片 1 的末尾,第二个字节在切片 2 的开头。

这样在分别解码两个切片时就会出现乱码问题:

可以看到,在两个切片之间有两个乱码字符,这就是因为文字被切割成两半造成的。

那么这种情况该如何处理呢?其实也很简单。

只要保证每次解码时都能够完整地包含每个文字即可。

具体做法如下:

async function loadNovel() {




  const url = 'https://duyi-static.oss-cn-beijing.aliyuncs.com/files/novel.txt';
  const resp = await fetch(url);




  const reader = resp.body.getReader();


  const decoder = new TextDecoder();

  let remainChunk = new Uint8Array(0);
  for (;;) {
    const { value, done } = await reader.read();
    if (done) {
      break;
    }
    const lastIndex = value.lastIndexOf(10);
    const chunk = value.slice(0, lastIndex + 1);
    const readChunk = new Uint8Array(remainChunk.length + chunk.length);
    readChunk.set(remainChunk);
    readChunk.set(chunk, remainChunk.length);
    remainChunk = value.slice(lastIndex + 1);
    const text = decoder.decode(readChunk);
    console.log(text);
  }

  const text = decoder.decode(remainChunk);
  console.log(text);
}

loadNovel();

有能力的同学呢,可以看一下,不理解的同学可以关注子辰,接下来会有相关的文章更新。

总结

以后当我们需要这种大数据要传输的话,如果说服务器那边不好改,那么客户端这边大致就是这个方向去处理,这样子一部分一部分把他读取出来,然后进行展示。

通过对本文的学习,子辰带着你更深入地了解浏览器中网络请求的底层原理,加深对浏览器工作原理的理解。同时,也可以提高你在前端开发中的编程能力和解决问题的能力,使得你能更好地完成工作任务。

当然在面试中,面试官也会常常问到白屏如何优化的问题。

那么这个问题的答案就是我们今天文章所讲的内容,组织一下语言用自己的话讲出来,惊艳面试官,才算有所收获。

本文来源

本文来源自渡一官方公众号:Duing,欢迎关注,获取最新最全最深入的技术讲解

感谢你阅读本文,如果你有任何疑问或建议,请在评论区留言,如果你觉得这篇文章有用,请点赞收藏或分享给你的朋友!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYxEM5Yl' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片