js的setTimeout和Promise---同步异步和微任务宏任务

栏目: JavaScript · 发布时间: 5年前

内容简介:久经前端开发沙场,会经历各式各样的需求,处理这些需求时候,会使用各种各样的api和功能,这里集中对setTimeout和Promise的异步功能进行讨论一下。这里就使用1.最初的试探

久经前端开发沙场,会经历各式各样的需求,处理这些需求时候,会使用各种各样的api和功能,这里集中对setTimeout和Promise的异步功能进行讨论一下。

单独使用的执行模式

这里就使用 Promise 作为例子,来探究一下单独使用它,会有哪些注意点。

1.最初的试探

执行代码, Promise 的基本使用:

let fn = () => {
    console.log(1)
  let a = new Promise((resolve, reject) => {
      console.log(2)
    resolve(3)
  })
  console.log(4)
  return a
}
// 执行
fn().then(data => console.log(data))

以上代码,输出结果为:

1 // 同步
2 // 同步
4 // 同步
3 // 异步

注意 new Promise() 是同步方法, resolve 才是异步方法。

此外,上面的方法,可以有下面这种写法,效果等同,主要是把 Promise 精简了一下:

let fn = () => {
  console.log(1)
  console.log(2)
  let a = Promise.resolve(3)
  console.log(4)
  return a
}

// 执行
fn().then(data => console.log(data))

因为现在讨论的是 Promise 的异步功能,所以下面均使用第二种写法的 Promise

2.多个同级 Promise

编辑器中,输入以下代码,多个同级的单层的 Promise

console.log('同步-0.1')
Promise.resolve().then(() => {
  console.log('P-1.1')
})
Promise.resolve().then(() => {
  console.log('P-1.2')
})
Promise.resolve().then(() => {
  console.log('P-1.3')
})
console.log('同步-0.2')

则会依次输出以下打印,毫无疑问的结果:

同步-0.1
同步-0.2
P-1.1
P-1.2
P-1.3

3. PromisePromise

复杂一下,新增行有注释说明:

console.log('同步-0.1')
Promise.resolve().then(() => {
  console.log('P-1.1')
  Promise.resolve().then(() => { // 新加行
    console.log('P-2.1') // 新加行
  }) // 新加行
})
Promise.resolve().then(() => {
  console.log('P-1.2')
  Promise.resolve().then(() => { // 新加行
    console.log('P-2.2') // 新加行
  }) // 新加行
})
Promise.resolve().then(() => {
  console.log('P-1.3')
  Promise.resolve().then(() => { // 新加行
    console.log('P-2.3') // 新加行
  }) // 新加行
})
console.log('同步-0.2')

输出结果如下:

同步-0.1
同步-0.2
P-1.1
P-1.2
P-1.3
P-2.1
P-2.2
P-2.3

可见,多层 Promise 是一层一层执行的。

4.为了最终确认,进行最后一次验证,在第一个 Promise 里面多加一层:

console.log('同步-0.1')
Promise.resolve().then(() => {
  console.log('P-1.1')
  Promise.resolve().then(() => {
    console.log('P-2.1')
    Promise.resolve().then(() => { // 新加行
      console.log('P-3.1') // 新加行
    }) // 新加行
    Promise.resolve().then(() => { // 新加行
      console.log('P-3.2') // 新加行
    }) // 新加行
  })
})
Promise.resolve().then(() => {
  console.log('P-1.2')
  Promise.resolve().then(() => {
    console.log('P-2.2')
  })
})
Promise.resolve().then(() => {
  console.log('P-1.3')
  Promise.resolve().then(() => {
    console.log('P-2.3')
  })
})
console.log('同步-0.2')

输出结果如下:

同步-0.1
同步-0.2
P-1.1
P-1.2
P-1.3
P-2.1
P-2.2
P-2.3
P-3.1
P-3.2

确认完毕,的确是一层一层的执行。

而且这里可以告诉大家, setTimeoutsetInterval 在单独使用的时候,和 Promise 是一样的,同样是分层执行,这里不再贴代码了(友情提醒: setInterval 的话,需要第一次执行就把这个定时器清掉,否则就无限执行,卡死页面秒秒钟的事儿),

混合使用的执行模式

接下来才是重点,下面将 setTimeoutPromise 进行混合操作。

console.log('同步-0.1')
Promise.resolve().then(() => {
  console.log('P-1.1')
})
setTimeout(() => {
  console.log('S-1.1')
});
Promise.resolve().then(() => {
  console.log('P-1.2')
})
setTimeout(() => {
  console.log('S-1.2')
});
console.log('同步-0.2')

执行结果如下。。。问题暴露出来了:

同步-0.1
同步-0.2
P-1.1
P-1.2
S-1.1
S-1.2

为什么,在同级情况下,是 Promise 执行完了 setTimeout 才会执行?

是人性的泯灭,还是道德的沦丧?

是因为JavaScript任务类型!

JavaScript的微任务和宏任务

敲黑板,标重点。

JavaScript的任务分为微任务(Microtasks)和宏任务(task);

  • 宏任务是主流,当js开始被执行的时候,就是开启一个宏任务,在宏任务中执行一条一条的指令;
  • 宏任务可以同时有多个,但会按顺序一个一个执行;
  • 每一个宏任务,后面都可以跟一个微任务队列,如果微任务队列中有指令或方法,那么就会执行;如果没有,则开始执行下一个宏任务,直到所有的宏任务执行完为止,微任务相当于宏任务的小尾巴;
  • 为什么有了宏任务,还会有微任务存在?因为宏任务太占用性能,当需要一些较早就准备好的方法,排在最后才执行的时候,又不想新增一个宏任务,那么就可以把这些方法,一个一个的放在微任务队列里面,在这个宏任务中的代码执行完后,就会执行微任务队列。

Promise 是微任务, setTimeout 是宏任务。

所以上面的代码中,代码执行时会是如下场景:

开始执行当前宏任务代码!

遇到了 Promise ?好嘞,把它里面的异步代码,放在当前这个宏任务后面微任务里面,然后继续执行咱的;

咦,有个 setTimeout ?是个宏任务,那在当前这个宏任务后面,创建第二个宏任务,然后把这个 setTimeout 里面的代码塞进去,咱继续执行;

咦,又一个 Promise ?把他塞进后面的微任务里。。。什么?已经有代码了?那有啥关系,继续往里塞,放在已有代码的后面就行,咱继续执行;

天啊,又来一个 setTimeout ,现在后面已经有第二个宏任务了对吧?那就创建第三个宏任务吧,后面再遇到的话,继续创建;

报告!代码执行到底了,当前这个宏任务执行完毕!

行,看一下咱的小尾巴---咱的微任务里面有代码吗?有的话直接执行;

报告,微任务里面,那两个 Promise 的异步代码执行完了!

干的漂亮。。。对了,刚刚微任务里面,有没有新的 Promise 微任务?有的话,继续在现在这个微任务后面放!对对,只看执行到的代码,有多少放多少,一会儿直接就执行了。。。如果遇到了 setTimeout 知道该怎么做吧?继续开宏任务!

报告,微任务全部执行完毕!

好!开始执行下一个宏任务!

所以,现在如果执行下面的代码,结果也显而易见吧:

console.log('同步-0.1')
Promise.resolve().then(() => {
  console.log('P-1.1')
  Promise.resolve().then(() => { // 新加行
    console.log('P-2.1') // 新加行
    Promise.resolve().then(() => { // 新加行
      console.log('P-3.1') // 新加行
    }) // 新加行
  }) // 新加行
})
setTimeout(() => {
  console.log('S-1.1')
});
Promise.resolve().then(() => {
  console.log('P-1.2')
})
setTimeout(() => {
  console.log('S-1.2')
});
console.log('同步-0.2')

执行结果如下:

同步-0.1
同步-0.2
P-1.1
P-1.2
P-2.1
P-3.1
S-1.1
S-1.2

无论 Promise 套用多少层,都会在下一个 setTimeout 之前执行。

Dom 操作到底是同步,还是异步?

这里出现一个说不清道不明的疑问, Dom 操作到底是同步操作还是异步操作?

如果是同步操作,那 vuenextTick 方法是做什么用的?不就是在Dom更新完之后的回调方法吗?

如果是异步操作,那在剧烈操作Dom后面的代码,为什么会被阻塞?而且代码看上去,也的确是按顺序执行的?

这里直接说明:js里面的Dom操作代码,是同步执行,但浏览器进行的Dom渲染,是异步操作。

浏览器渲染Dom和执行js,同时只能二选一,渲染一次Dom的时机是,当前宏任务和小尾巴微任务执行完,下一个宏任务开始前

vuenextTick 方法,则是使用H5的Api--- MutationObserver

,监听浏览器将Dom渲染完成的时机。

若浏览器不支持此方法,则会使用 setTimeout ,把 nextTick 回调函数的执行时机,作为一个宏任务;

上面也说了,浏览器渲染一次Dom,是下一个宏任务开始前,这样使用了 setTimeout ,保证了Dom确实渲染完成。

这里也需要稍作提醒,js操作Dom是同步的,但操作Dom,毕竟超出了js本身语言的Api,每操作一次Dom,都需要消耗一定的性能,所以,在适合的情况下,最好先把要修改的Dom的内容,以字符串或者虚拟Dom的形式拼接好,然后操作一次Dom,把组装好的Dom字符串或虚拟Dom,一次性的塞进HTML页面的真实Dom中。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

谷歌和亚马逊如何做产品

谷歌和亚马逊如何做产品

梅 (Chris Vander Mey) / 刘亦舟 / 人民邮电出版社 / 2014-6-1 / CNY 49.00

软件在交付之前,面临产品、方案、项目和工程管理等诸多挑战,如何做到游刃有余并打造出极致产品?本书作者曾任谷歌和亚马逊高级产品经理、现任Facebook产品经理,他将自己在达特茅斯学院钻研的理论知识和在领先的互联网公司十年的工作经验尽数总结在此,从定义产品开始,一步步指导你完成管理项目、迭代、发布、市场推广等交付流程,让你身临其境地体验到极致产品如何取得成功。 本书主要内容: 如何清晰定......一起来看看 《谷歌和亚马逊如何做产品》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具