js异步从入门到放弃实践篇-常见写法面试题解析

11次阅读

共计 3653 个字符,预计需要花费 10 分钟才能阅读完成。

前文

该系列下的前几篇文章分别对不同的几种异步方案原理进行解析,本文将介绍一些实际场景和一些常见的面试题。(积累不太够,后面想到再补)

正文

流程调度(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()

注意辨析这里 concurrentScheduleByAsyncserialScheduleByAsync的区别,关键点是 同一个 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)。

小结

这篇文章主要是给这个系列做个简单的收尾,单独纯异步的问题难点其实也不多,偷个懒,后面想到了再补上。


如果觉得写得不好 / 有错误 / 表述不明确,都欢迎指出
如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处。如果有问题也欢迎私信交流,主页有邮箱地址
如果觉得作者很辛苦,也欢迎打赏~

正文完
 0