前端开发核心知识进阶 2.5 异步不可怕,“死记硬背”+实践拿下

异步是前端开发中的一个重点内容, 也是难点之一。为了更优雅地实现异步,JavaScript语言在各个历史阶段进行过多种尝试,但是由于异步天生具有一定的“复杂度”,使得开发者并不能够轻松地吃透相关的理论知识并上手实践。在理论方面,我们知道JavaScript 是单线程的,那它又是如何实现异步的呢?在这个环节中,浏览器或Node.js又起到了什么样的作用?什么是宏任务?什么是微任务?在实践方面,从callback到Pormise,从Gemeator到aypc/aia,到底应该如何更优雅地实现异步操作?下面让我们来一-探究竟。
 

一、红绿灯任务控制

 红灯亮三秒,黄灯亮俩秒,绿灯亮一秒,如何让三个灯交替重复地亮呢

先声明亮灯函数


function green(){
    console.log('绿灯亮');
}
function yellow(){
    console.log('黄灯亮');
}
function red(){
    console.log('红灯亮');
}

1.回调实现

function callback(){
    green()
    setTimeout(()=>{
        yellow()
        setTimeout(()=>{
            red()
            setTimeout(()=>callback(),3000)
        },2000)
    },1000)
}

2.Promise实现

const task = (timer, light) => new Promise((res,rej) => {
        if(light == 'red'){
            red()
        }else if(light == 'yellow'){
            yellow()
        }else{
            green()
        }
        setTimeout(()=>{
            res()
        },timer)
    })
function promiseFun(){
    task(1000,'green')
    .then(()=> task(2000,'yellow') )
    .then(()=> task(2000,'red'))
    .then(promiseFun)
}

3.async/await实现

const task = (timer, light) => new Promise((res,rej) => {
        if(light == 'red'){
            red()
        }else if(light == 'yellow'){
            yellow()
        }else{
            green()
        }
        setTimeout(()=>{
            res()
        },timer)
    })

async function awaitFn(){
    await task(1000,'green')
    await task(2000,'yellow') 
    await task(2000,'red')
    awaitFn()
}

awaitFn()

二、实现图片预加载

先编写图片预加载函数,返回一个Promise,在Image的load事件中对Promise进行决议,先绑定load事件再赋值src是为了防止有浏览器缓存时,加载太快导致load事件不被触发

const loadImg = (url) => {

    const baseUrl = `http://www.image.com/` //接口头

    return new Promise((res,rej)=>{
        var img = new Image()
        img.onerror = () =>{
            console.log('图片加载失败');
        }
        img.url = url
        img.onload = () => {
            res(img.url)
        }
        img.src = baseUrl + url
    })

}

1.Promise依次预加载图片

urlArr.reduce((pre,cur) => pre.then(() => loadImg(cur)), Promise.resolve())

当前图片的url传参给loadImg,返回一个Promise,Promise决议后在then中将下一张图片的url传参给loadImg,又返回个Promise,这样重复下去

2.await依次预加载图片

var awaitImg = async () => {
    for( i of urlArr){
        await loadImg(i)
    }
}

3.Promise.all进行图片并发预加载

const promiseArr = urlArr.map(item => loadImg(item))
Promise.all(promiseArr)
.then(()=> console.log('全部加载完成'))
.catch(() => console.log('出错了'))

Promise.all参数为Promise的数组,只有所有Promise都resolve了,才会触发then,否则触发catch

4.Promise.race和Promise.all进行图片并发预加载限制

const imgLoadLimit = (urlArr, limit) => {

    const urlArrCy = [...urlArr]

    if(urlArrCy.length <= limit){
        const promiseArr = urlArrCy.map( url => {
            var pro = loadImg(url)
            pro.id = url
            return pro
        })
        return Promise.all(promiseArr)
    }

    const promiseArr = urlArrCy.splice(0, limit).map( (url) => {
        var pro = loadImg(url)
        pro.id = url
        return pro
    })

    urlArrCy.reduce( (prePromise,curUrl) => 
        prePromise.then(() => Promise.race(promiseArr))
        .catch( err => console.log(err))
        .then((resUrl) => {
            let promiseIndex = promiseArr.findIndex( item => item.id == resUrl)
            promiseArr.splice(promiseIndex,1)
            var pro = loadImg(curUrl)
            pro.id = curUrl
            promiseArr.push(pro)
        })
    ,Promise.resolve())
    .then(() => Promise.all(promiseArr))
}
imgLoadLimit(urlArr,3)

数组小于限制数,直接.Promise.all,否则裁剪数组前几位,数量为限制数,进行Promise.race。

Promise.race参数也是Promise数组,它的then的返回值是数组里第一个决议的Promose的值。

每次Promise决议后,通过标识找到它在数组里的位置,把它删去,再把未预加载的图片的Promise放进去,继续Promise.race。等最后一张图片的Promise放进数组后,进行Promise.all。

三、setTimeOut相关考查

1.JS分为同步任务和异步任务,同步任务形成执行栈,异步任务只会等同步任务执行完,也就是执行栈空闲才会执行

setTimeout(()=> console.log('setTimeOut'),1000)
console.log(2222);
while(1){

}
console.log(1111);

这里执行栈不会空闲,所以setTimeOut不会执行


setTimeout(() => {
    while(1){

    }
},1000)
console.log(1111);

这里console.log(1111);会执行,但在setTimeOut里卡线程了,setTimeOut之后的代码不会执行


const t1 = new Date()
setTimeout(() => {
    const t3 = new Date()
    console.log('t3 - t1:',t3-t1); //t3 - t1:200
},100)
let t2 = new Date()
while(t2 - t1 < 200){
    t2 = new Date()
}

setTimeOut定时100ms后不会执行,等200ms执行栈空闲才执行


  2.setTimeOut的细节

setTimeout(() => console.log('setTimeOut1'),200)
setTimeout(() => console.log('setTimeOut2'),100)

//setTimeOut2
//setTimeOut1

虽然第一个setTimeOut先进队列,但第二个setTimeOut定时比较早结束,所以先触发


setTimeout(() => console.log('setTimeOut1'),1)
setTimeout(() => console.log('setTimeOut2'),0)

//setTimeOut1
//setTimeOut2

因为1ms和0ms可以视为等价,所以第一个定时器先执行


3.宏任务和微任务

任务队列的异步任务又分宏任务和微任务,它们虽都是异步任务,都在任务队列中,又处于不同的队列中,宏任务队列和微任务队列

代码执行中,将同步任务放主线程形成执行栈,遇到的微任务放微任务队列,遇到的宏任务放宏任务队列,同步任务执行完,执行栈空闲,开始执行微任务,微任务都执行完,开始执行宏任务 

宏任务包括:setTimeOut、setInterval、I/O、事件、postMessage、setImmediate(Node.js的特性,浏览器已废弃该api)、requestAnimationFrame、Ui渲染

微任务包括:Promise.then、MutationObserver、process.nextTick(Node.js)

例:

console.log('start');

setTimeout(() => console.log('setTimeOut1'),200)

let foo = () => new Promise((res, rej) => {

    console.log('first promise');
    new Promise((res,rej) => {

        console.log('second promise');
        setTimeout(() => console.log('setTimeOut2'),100)
        res('second promise then')

    }).then(res => console.log(res) )

    res('first promise then')

})
foo().then(res => console.log(res))

console.log('end');


//输出结果
start
first promise
second promise
end
second promise then
first promise then
setTimeOut2
setTimeOut1

总结

异步任务的处理,因其重要性,在前端开发中始终是一个 不可忽视的考查点;又因其复杂性
点活多变,开发者需要熟悉各种异步方案。同时,每一种异步方案都是相辅相成的。如果你
没有完全理解callback,那也许就很难理解Promise;如果没有熟练掌握Promise, 那就更无从谈起会使用Generator和async/await.

很多异步场景都涉及网络、高风险计算,但本篇还没有涉及异步中错误处理这个重要内容,这方面的信息会在后续内容中进行讲解。异步的整个学习过程需要我们从最基础的知识开始,步步为营。如果学习一次理解不了, 那就学习两次、三次。相信我,这一定是一个吃经验和重复次数的“水滴石穿”的过程。通过次次“死记硬背”,我们会慢慢理解其中的道理。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值