JS异步编程(async、await详解)

function loadImage(src, reslove, reject) {

const image = new Image()

image.src = src

image.onload = reslove(image) // 这时候就相当于是直接同步代码了 因为直接调用了reslove这个方法,就不异步了

image.onload = () => {

reslove(image)

} // 这样就是正常的异步的将src又给传了出去

/*

两个onload的区别 如果使用第一onload 打印结果是

先打印 http://39.96.42.170 之后才打印 图片加载成功了 Image对象

*/

image.onerror = reject

}

loadImage(

‘http://39.96.42.170/bg.jpg’,

() => {

console.log(‘图片加载成功了’)

},

() => {

console.log(‘图片加载失败’)

}

)

console.log(‘http://39.96.42.170’)

/*

执行结果: 先打印 http://39.96.42.170 之后才打印 图片加载成功了或是图片加载失败

*/

注意点(重点):

  • 我们写的回调函数,如果函数里面直接执行了回调函数,那就不异步了

  • 正确的方法应该是在函数中嵌套一个函数,再将值传递出去

通过上面的例子就可以很清晰的知道了,因为加载图片相对来说是一个耗时的,由于单线程的特性,如果等到第加载完成了之后再执行后面的逻辑,页面必定是卡顿的,用户体验自然也就不行了,所以 JS 异步就是为了解决这个。

定时器任务轮询案例

JS 中的定时器有两种,分别是setTimeOut()setInterval()这两种,我们发现,使用这两个方法的时候我们都是传递回调函数,这就原因也是很好理解,因为定时是耗时的,不可能去等这个执行完才去执行别的任务,所以就采用回调函数异步的方式来编写的方法

function interval(callback, delay) {

let timeId = setInterval(() => {

callback(timeId) // 通过异步的形式 将timeId 传递出去

}, delay)

}

interval(timeId => {

const div = document.querySelector(‘div’)

let left = div.offsetLeft

div.style.left = left + 10 + ‘px’

console.log(‘left’, left)

if (left + div.offsetWidth >= document.body.clientWidth) {

clearInterval(timeId)

interval(timeId => {

let width = div.offsetWidth

div.style.width = width - 10 + ‘px’

if (width <= 20) {

clearInterval(timeId)

}

}, 100)

}

}, 50)

/*

这里写的延迟执行时长是50ms 但是实际并不是一定是50ms 这个也是个不确定的因素,会等主线程执行结束了之后,

并且任务队列循环到这个任务之后,等待50ms才执行

如此例,必须主线程for循环执行结束了之后才会执行异步任务队列的任务,而主线程可能就会执行好久

*/

for (let i = 0; i < 20000000; i++) {

console.log(i)

}

复习 DOM:

  • DOM.offsetLeft DOM.offsetWidth 都是属性,获取一个 DOM 元素的 left 属性值和宽度

  • document.body.clientWidth 获取浏览器窗口的宽度

注意点(重点):

  • 定时器上规定的时间并不代表一定过这么久就会执行,还要看主线程代码执行的时间和轮询队列的排列

回调地狱的产生

在 JS 中,主线程是从上到下执行的,遇到加载的时候会交给异步加载模块去进行加载,而异步加载模块就没有说先进先出,后进后出的原则的,它的原则就是谁快,就先出谁。

function aMode() {

console.log(‘a模块’)

}

function bMode() {

aMode()

console.log(‘bMode 执行了’)

}

function load(src, reslove, reject) {

let script = document.createElement(‘script’)

script.src = src

script.onload = reslove

script.onerror = reject

document.body.appendChild(script)

}

load(

‘./module/a.js’,

() => {

aMode()

},

() => {

console.log(‘线程加载失败’)

}

)

load(‘./module/b.js’, () => {

bMode()

})

load(‘./’)

console.log(‘主线程’)

很明显,模块 b 是依赖与模块 a 执行的,因为执行模块 b 的时候,会执行模块 a 的aMode()方法,而再看我们主逻辑代码这,虽然我们代码的顺序是先写了加载 a 模块,再加载 b 模块,但是由于异步加载模块的执行顺序并不是先看到谁执行谁,而是谁先执行完就先出谁,所以以上的代码有一定的几率会报错,如图

  • 错误加载时

加载错误时

  • 正确加载时

正确加载时

所以,为了解决这个加载快慢的因素,我们就只能等一个模块加载完成之后再去加载第二个模块,就是这样,要实现这个就只能在加载成功的回调函数里面去加载其他的模块,这样就能够保证第一个被依赖的模块一定是已经加载完成的

load(‘./module/a.js’, () => {

// 只要走到这个回调里面,就说明一定时已经拿到了值

load(‘./module/b.js’, () => {})

})

Promise 机制

Promise 会将创建的任务是微任务,微任务的重要性是大于宏任务的。也就是说当又有宏任务又有微任务的情况下,浏览器会优先的执行微任务队列里面的任务

new Promise((resolve, reject) => {

resolve(‘操作成功’)

})

.then(

() => {

console.log(‘成功的回调1’)

},

() => {

console.log(‘失败的回调’)

}

)

.then(() => {

console.log(‘成功的回调2’)

})

知识点:

  • 从结构上,解决了回调地狱,代码的结构不再是向右边倾斜的,而是从上到下的,并且执行的逻辑也是从上到下的

  • Promise 的 then(callback1,callback2) 里面的两个回调分别是成功的回调和失败的回调,即是resolve()reject()处理的结果的返回,可以接收到对应返回的参数

  • 浏览器的执行顺序 主线程>微任务>宏任务

主线程、微任务、宏任务执行顺序

执行顺序前面已经学习过了,就是主线程>微任务>宏任务

这里的例子可以想象成主线程是至尊 VIP,微任务是 VIP,宏任务就只是普通顾客,即使排队排在最前面,也得给 VIP 让路,让 VIP 先体验

// 执行输出结果:Promise的结构体中也是主线程、主线程、微任务 Promise、宏任务 setTimeout

知识点(细节)

  • 执行顺序:主线程>微任务>宏任务

  • Promise 的函数体也是主线程,并不是 Promise 的所有都是微任务,只有他resolve()reject()的回调部分才是微任务

  • 即使setTimeou()的延迟时间是 0,并且写在最前面,也是得等主线程和微任务执行完了之后才会去执行

  • Promise 的状态是单向的,不可逆的,一旦触发了一个状态之后就不可以进行更改状态了

Promise 的 then()方法细节

Promise()的 then()方法返回的值默认也是一个 Promise

new Promise((resolve, reject) => {

resolve(‘请求成功1’)

})

.then(

res => {

return ‘请求成功’ // 只要then 里面有return 结果就可以继续使用then来接收

},

err => {}

)

.then(res => {

console.log(‘kkk’, res)

})

// 执行结果 kkk 请求成功

  • 只要 then 里面有 return 结果就可以继续使用 then 来接收,不论是第一个成功的回调的 return 还是 第二个失败的回调的 return 只要在 then 一下就可以拿到那个 return 的值

new Promise((resolve, reject) => {

resolve(‘请求成功1’)

})

.then(

res => {

return new Promise((resolve, reject) => {

reject(‘失败了’)

})

},

err => {}

)

.then(null, err => {

console.log(‘失败的结果’, err)

})

//6-Promise的then.html:31 失败的结果 失败了

  • 正常的使用,then 回调里面 return 一个新的 Promise,这样因为 return 的是一个 Promise,所以我们就可以继续的链式的在后面继续一直的 then 下去了

Promise 多种错误监听方式与 catch 总监听

Promise 的 resolve 和 reject 状态分别可以在 then 方法的两个回调函数中捕获,但是除此之外还可以使用 catch()函数中来进行总获取

let p1 = new Promise((resolve, rejetc) => {

// resolve(‘处理成功’)

rejetc(‘处理失败’)

})

p1.then(res => {

return new Promise((resolve, reject) => {

reject(‘error’)

})

}).catch(err => {

console.log(‘catch总接收’, err)

})

// 打印结果:catch总接收 处理失败

知识点:

  • catch 是对整个 promise 流的一个总的错误监听,如果在 then 中没有写第二个错误处理的函数,那么这个错误会走到 catch 这里

  • 因为 catch 是可以实现对整个 Promise 流的监听错误,所以 catch 正常情况下都是写在最后面的

  • 如果一个 promise 的 then 中有对错误的处理,那么执行的顺序是会选择走自己的错误处理,原因也很好理解,自己有错误处理就用自己的,没有就用公用的

let p2 = new Promise((resolve, reject) => {

jimmy()

// try {

// jimmy()

// } catch (error) {

// reject(error)

// }

})

p2.then(

res => {

console.log(res)

},

err => {

console.log(err.message)

}

)

// 输出结果:jimmy is not defined

的确是存在逻辑错误,调用了一个没有定义的函数,但是我们会发现自动的走到了错误处理函数上,我们可以理解是在 promise 内部封装了我们看不见的 try-catch 模块,进入 catch 的时候会自动的 reject 出来错误信息

小案例 – 使用 Promise 封装一个 ajax

使用 ajax 就避免不了回调地狱,所以使用 Promise 来封装一下 ajax 是一个优解,axios 就是通过 Promise 封装得来的

// 要有面向对象思想,万物皆可封装成为一个我们自己的东西

class HttpError extends Error {

constructor(msg) {

super(msg) // 这里的msg 会被默认放在 message 属性里面

this.name = ‘接口错误’

}

}

class ParamsError extends Error {

constructor(msg) {

super(msg)

this.name = ‘参数错误’

}

}

function ajax(url) {

return new Promise((resolve, reject) => {

if (!/^https?/.test(url)) {

reject(new ParamsError(‘请求地址错误’))

}

let xml = new XMLHttpRequest()

xml.open(‘GET’, url)

xml.send()

xml.onload = () => {

console.log(‘kkk’, xml)

// 箭头函数this指向的是 调用这个函数对象的this

if (xml.status == 200) {

resolve(xml.response)

} else if (xml.status == 404) {

reject(new HttpError(‘请输入正确的接口’))

} else {

reject(‘加载失败’)

}

}

})

}

ajax(‘https://jsonplaceholder.typicode.com/posts’)

.then(res => {

console.log(res)

})

.catch(err => {

// instanceof 能够判断该对象对象是从哪个类实例出来的

/*

最典型的就是可以用来判断是对象还是数组

*/

if (err instanceof ParamsError) {

console.log(err)

} else if (err instanceof HttpError) {

alert(err)

}

})

总结:

  • 这个案例虽小,但是涉及到的东西还是很多的,如创建自己的错误类,箭头函数的指向,创建基础的 ajax,instanceof关键字的使用

  • Array 是继承 Object 类的 object 对象是通过实例化 Object 实现的,所以我们呢可以通过 对象是否是通过 Array 实例化出来的 来判断是否数组还是普通对象

finally 关键名

finally 表示无论 promise 请求的状态是成功还是拒绝都会执行的的代码块,就十分的类似 try-catch-finally

  • 这个关键字可以做很多的操作,一个比较常用的操作就是可以封装一个加载动画,每次执行异步请求的时候显示这个动画,finally 的时候将这个动画给取消掉。

封装定时器

function timeout(delay) {

return new Promise((resolve, reject) => {

setTimeout(resolve, delay)

})

}

// 使用promise 不会造成会回调地狱 是一个竖装从下到下的执行过程 整个过程十分清晰

timeout(2000)

.then(() => {

最后

面试一面会问很多基础问题,而这些基础问题基本上在网上搜索,面试题都会很多很多。最好把准备一下常见的面试问题,毕竟面试也相当与一次考试,所以找工作面试的准备千万别偷懒。面试就跟考试一样的,时间长了不复习,现场表现肯定不会太好。表现的不好面试官不可能说,我猜他没发挥好,我录用他吧。

96道前端面试题:

常用算法面试题:

前端基础面试题:
内容主要包括HTML,CSS,JavaScript,浏览器,性能优化

Array 实例化出来的 来判断是否数组还是普通对象

finally 关键名

finally 表示无论 promise 请求的状态是成功还是拒绝都会执行的的代码块,就十分的类似 try-catch-finally

  • 这个关键字可以做很多的操作,一个比较常用的操作就是可以封装一个加载动画,每次执行异步请求的时候显示这个动画,finally 的时候将这个动画给取消掉。

封装定时器

function timeout(delay) {

return new Promise((resolve, reject) => {

setTimeout(resolve, delay)

})

}

// 使用promise 不会造成会回调地狱 是一个竖装从下到下的执行过程 整个过程十分清晰

timeout(2000)

.then(() => {

最后

面试一面会问很多基础问题,而这些基础问题基本上在网上搜索,面试题都会很多很多。最好把准备一下常见的面试问题,毕竟面试也相当与一次考试,所以找工作面试的准备千万别偷懒。面试就跟考试一样的,时间长了不复习,现场表现肯定不会太好。表现的不好面试官不可能说,我猜他没发挥好,我录用他吧。

96道前端面试题:

  • [外链图片转存中…(img-JRDujAg1-1718180912497)]

常用算法面试题:

  • [外链图片转存中…(img-Tw2fnti7-1718180912498)]

前端基础面试题:
内容主要包括HTML,CSS,JavaScript,浏览器,性能优化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值