原贴链接:javascript异步总结归档

这是javascript异步系列文章的第十篇,也是最后一篇,总结并归档。
十篇文章不足以把异步解释清清楚楚,
异步这块知识点在js中占比很大,很多莫名其妙的bug也出现在这里,
比如说下面的这个栗子:

一个bug

重点看郭靖

  1. let Hero = {
  2. gj: "初始化郭靖",
  3. hr: {
  4. name: "黄蓉",
  5. sex: "女"
  6. }
  7. };
  8. function demo(obj) {
  9. return new Promise((resolve, reject) => {
  10. resolve("赋值郭靖");
  11. }).then(res => {
  12. obj.gj = res;
  13. console.log("then-Hero.gj:", Hero.gj);
  14. });
  15. }
  16. demo(Hero);
  17. console.log("Hero:", Hero);
  18. console.log("Hero.gj:", Hero.gj);

会输出什么?

image.png

重点看郭靖
先输出Hero,在输出Hero.gj
梳理一下代码执行流程

  • 先执行demo(Hero);
  • 进入Promise, resolve(“赋值郭靖”);
  • “赋值郭靖”作为Promise的返回值
  • then 是异步,进入异步队列
  • 继续走同步的主线程,console.log(“Hero:”, Hero);Hero没有进行赋值操作,所以Hero值没变
  • 继续走同步的主线程,console.log(“Hero.gj:”, Hero.gj);Hero没有进行赋值操作,所以Hero.gj值依然没变=>初始化郭靖
  • 同步执行完毕,开始执行事件循环中的异步事件=>then,then 方法中对Hero中的gj进行赋值,
  • 所以 console.log(“then-Hero.gj:”, Hero.gj);//=>赋值郭靖

我相信你一定遇到过类似的bug,这是哪里出了问题?
首先,肯定是异步出了问题,但是这个不只是异步的问题

引用类型

再来一个栗子

  1. let Hero = {
  2. let gj = "初始化郭靖";
  3. function demo1(str) {
  4. return new Promise((resolve, reject) => {
  5. resolve("赋值郭靖");
  6. }).then(res => {
  7. gj = res;
  8. console.log('then-gj:',gj);
  9. });
  10. }
  11. demo1(gj);
  12. console.log("gj:", gj);

看下输出:

  1. //=>gj: 初始化郭靖
  2. //=>then-gj: 赋值郭靖

如果这看不出是引用类型的问题,可以试试这个

  1. console.log("Hero:", Hero);
  2. console.log("str-Hero:", JSON.stringify(Hero));

看下输出:

image.png

Hero是引用类型,如果单单只是引用类型,也没什么,可是引用类型遇到了console.log,就出问题了

console.log

看一个小栗子

  1. const json = { a: 1, b: 2 };
  2. console.log(json);
  3. json.a = 3;

看下输出:

image.png

看到这个输出是不是很开心?当你把这个json展开的时候,
惊喜出现了
image.png

console.log在打印应用类型的时候,可能会不太靠谱
最上面的栗子,我们使用debugger试一下

  1. //省略
  2. demo(Hero);
  3. console.log("Hero:", Hero);
  4. debugger
  5. console.log("Hero.gj:", Hero.gj);

这次再看下输出:

image.png

现在看来,似乎全是console.log的问题,和引用类型和异步没关系

解决方案

这个bug的出现的根源是异步出现了问题
修改下代码(具体根据自己的需求进行修改,如下代码仅供参考)

  1. let Hero = {
  2. gj: "初始化郭靖",
  3. hr: {
  4. name: "黄蓉",
  5. sex: "女"
  6. }
  7. };
  8. function demo(obj) {
  9. return new Promise((resolve, reject) => {
  10. resolve("赋值郭靖");
  11. })
  12. .then(res => {
  13. obj.gj = res;
  14. console.log("then-Hero.gj:", Hero.gj);
  15. })
  16. .then(() => {
  17. console.log("Hero:", Hero);
  18. console.log("Hero.gj:", Hero.gj);
  19. });
  20. }
  21. demo(Hero);

输出:

image.png

关于异步有一篇深入的介绍
深入核心,详解事件循环机制

再说一下引用类型

在vue项目中会有一些规则,例如

  • 子组件不能修改父组件的值
  • state只能在mutation中修改

至于为什么这么要求,我们不在这里讨论,我们要说的是,如果你不这么做,vue就会抛出警告
但是引用类型除外,
如果一个变量是引用类型,在子组件中修改,vue不抛出警告
如果state是个引用类型,在mutation外部修改,vue不抛出警告
但是最好不要这么做,否则日后很难定位bug根源

异步

我们说起异步,之前首先想到的是回调函数,但是回调函数存在各种问题, ES6中出现了Promise,解决了回调函数问题,Promise中我们又介绍了几个常用的API
Promise.all()、Promise.race()、Promise.finally()、Promise.then()、Promise.catch()
我们后来又介绍了Generator,通过Generator引出了async await,
async就是Generator的语法糖,简化了Generator的使用方法
async无法取代Promise,async的使用依赖于Promise
有人说async await是处理异步的终极方案,这个说法有些极端
处理异步的方法我们介绍很多,没有最好的,只有最合适的
会的多了,选择也就多了,代码质量自然就会高
所以,这几种异步的处理方式我们都要学会
关于JS异步就介绍到这里
END