前文
该系列下的前几篇文章分别对不同的几种异步方案原理进行解析,本文将介绍一些实际场景和一些常见的面试题。(积累不太够,后面想到再补)
正文
流程调度(schedule)
流程调度,最常见的就是继发和并发(或者说串行和并行)两种类型,在日常工作里都很常见。接下来结合实际场景进行说明:
1. 串行执行一系列异步操作,每一步依赖前一步的结果
串行执行的关键是,将每一个异步任务放到前一个异步任务的回调函数里执行。
- 场景:一串连续的动画,每个动画必须等待前一个动画完全执行完,并且如果某个动画执行失败,则不继续执行下一个动画。
- 代码:
// 这里假定一共要执行5个动画 // getAnimation 函数模拟执行动画 接收参数i表述动画编号 返回一个promose const getAnimation = (i) => new Promise((resolve, reject) => { setTimeout(()=>{ // 随机返回true或者false const isSuccess = Math.random() > 0.5 console.log(`第${i}个动画执行`) if(isSuccess){ return resolve(isSuccess) } return reject(isSuccess) },1000) }) // 1.promise实现 核心就是嵌套代码 const serialScheduleByPromise = () => { let p = Promise.resolve(true) const tasks = [] for(let i=0;i < 5; i++){ p = p.then(isSuccess=>{ if(isSuccess){ return getAnimation(i+1) } }).catch((err)=>{ return console.log(`执行失败`) }) } } serialScheduleByPromise() // 2.async/await实现 const serialScheduleByAsync = async () => { try{ for(let i=0;i < 5; i++){ await getAnimation(i+1) }}catch(e){ console.log(`执行失败`) } } serialScheduleByAsync()
async/await
的语法虽然没有单独解析,但是本质就是前一篇介绍的带自动执行器的generator
而已,因此不再赘述
可以看到,async的写法代码更简洁,而且逻辑更清晰,可读性更强。
2. 并行执行所有异步操作,等到所有请求完成后,按照读取请求的顺序输出结果
场景:并发读取5个数据(为了方便 分别编号为1-5),然后按照实际读取顺序结果
const getDataById = (i) => new Promise((resolve, reject) => { // 随机延迟一个时间返回结果, const delay = Math.floor(Math.random() * Math.floor(3000)) // 延迟时间可能为 0,1000,2000 毫秒 setTimeout(()=>{ return resolve(i) }, delay) }) // 1.promise实现 const concurrentScheduleByPromise = ()=>{ const promises = [] const result = [] for(let i = 0;i < 5;i++){ promises[i] = getDataById(i+1) promises[i].then(i=>{ result.push(i) }) } Promise.all(promises).then(()=>{ result.forEach(id=>{ console.log(id) }) }) } concurrentScheduleByPromise() // async/await实现 const concurrentScheduleByAsync = () => { for(let i = 0 ;i < 5; i++){ let task = async function (){ console.log(await getDataById(i+1)) } task() } } concurrentScheduleByAsync()
注意辨析这里concurrentScheduleByAsync
和serialScheduleByAsync
的区别,关键点是同一个async
函数内部的await
才是按顺序执行。
流程调度里比较常见的一种错误是“看似串行”的写法,可以感受一下这个例子:
const getPromise = (name) =>new Promise(resolve=>{ setTimeout(()=>{ console.log(name) resolve(name) },1000) }) // 判断以下几种写法的输出结果 Promise.resolve().then(getPromise('1a')).then(getPromise('1b')).then(getPromise('1c')) Promise.resolve().then(()=>getPromise('2a')).then(()=>getPromise('2b')).then(()=>getPromise('2c')) Promise.resolve().then(getPromise('3a').then(getPromise('3b').then(getPromise('3c')))) Promise.resolve().then(()=>getPromise('4a').then(()=>getPromise('4b').then(()=>getPromise('4c'))))
辨别输出顺序
这类题目一般出现在面试题里。
1. 基础-区分不同任务类型
console.log(1) new Promise(resolve => { console.log(2) setTimeout(() => { console.log(10) }, 10) resolve() console.log(3) }).then(() => { console.log(5) }) setTimeout(() => { console.log(7) Promise.resolve().then(() => { console.log(9) }) console.log(8) }) Promise.resolve().then(() => { console.log(6) }) console.log(4) // 输出 1 2 3 4 5 6 7 8 9 10
2. 复杂-加入浏览器render
<style> .outer { padding: 30px; background-color: aqua; } .inner { height: 100px; background-color: brown; }</style><body> <div class="outer">outer <div class="inner">inner</div> </div></body><script> var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); // Let's listen for attribute changes on the // outer element new MutationObserver(function () { console.log('mutate'); }).observe(outer, { attributes: true }); // Here's a click listener… function onClick() { console.log('click'); setTimeout(function () { console.log('timeout'); }, 0); Promise.resolve().then(function () { console.log('promise'); }); outer.setAttribute('data-random', Math.random()); } // …which we'll attach to both elements inner.addEventListener('click', onClick); outer.addEventListener('click', onClick); // innner.click() 试试直接点击和js执行click的区别</script>
这类问题实质上就是辨析异步任务队列类型,详细内容和解析可以直接看js异步从入门到放弃(三)- 异步任务队列(task queues)。
小结
这篇文章主要是给这个系列做个简单的收尾,单独纯异步的问题难点其实也不多,偷个懒,后面想到了再补上。
如果觉得写得不好/有错误/表述不明确,都欢迎指出
如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处。如果有问题也欢迎私信交流,主页有邮箱地址
如果觉得作者很辛苦,也欢迎打赏~