~为什么需要异步操作?
比起同一时间只能干一件事,我们总是追求更高的效率,一边吃饭一遍写代码还能看直播。
比如说当我们请求一个接口,后台响应的时间非常长,如果没有异步操作,程序只能傻等,进行不了下一步操作。
Js只有一个主线程执行,如果说我们能把复杂的需要时间处理的任务,交给一个模块去处理,不占用主线程,等它处理结束后,再得到响应就好了
举个例子:我们要做吃饭、洗衣服、和看直播这三件事
这三件事就会放入我们的任务队列中,如果以同步的思想来看的话,我们只能先吃饭再洗衣服,后看直播。这很傻,意味着我们做一件事情时不能做其他的。
我们可以看直播,把衣服交给洗衣机,做饭交给电饭煲。等他们做好后滴的一声提示我,去晾衣服,和吃饭。
需要注意的是,衣服可以交给洗衣机,就像等待服务器响应一样我可以做其他的东西。但是最后晾衣服完成洗衣服的动作,任然是需要主线程处理的。
~当主线程中的同步代码代码执行时间十分长
function a(){
console.log(1)
}
function b(){
console.log(2)
}
a()
b()

function a(){
for(let i=0;i<1000000000;i++){
if(i==999900000){
console.log(i)
}
}
}
function b(){
console.log(2)
}
a()
b()

function a(){
setTimeout(()=>{
for(let i=0;i<1000000000;i++){
if(i==999900000){
console.log(i)
}
}
},0)
}
function b(){
console.log(2)
}
a()
b()

~eg:异步加载图片
function loadImg(url,resolve,reject){
let img = new Image()
console.dir(img)
img.src=url
img.onload=()=>{
resolve(img)
}
img.onerror=reject
}
loadImg('https://i1.hdslb.com/bfs/face/a3d75e10c363fccfe994b708ddb19caa2e6e1c5e.jpg@52w_52h.webp',
(img)=>{
document.body.appendChild(img)
console.log('resolve')
},
()=>{
console.log('reject')
},
)
console.log(123)
处理图片的时间显然很长,Js将其转为异步执行,这其中可能执行成功可能失败,我们用回调接收响应。就上文所说,做饭交给电饭煲,做好了通知我去吃,做糊了我去重做,期间我们可以去做的别的事情,就像下面执行的同步代码输出123。
~当两个异步操作同时触发,如何控制先后
function js(url,call){
let script=document.createElement('script')
script.src=url
document.body.appendChild(script)
script.onload=call
}
js('one.js',()=>{
one()
})
js('two.js',()=>{
two()
})


如果想让two在one.js加载后再加载,最简单的方法就是,在one的回调中,加载two
js('one.js',()=>{
one()
js('two.js',()=>{
two()
})
})
但是这样有一个问题,当业务逻辑非常复杂时,不停的在嵌套,出现回调地狱有两个问题,第一是代码非常不美观冗余,第二当在多层嵌套中,有一层出现了问题,我们无法处理捕获错误,只能在其中判断,造成代码更加冗余。

Promise
~用来做什么?
从上面的内容我们可以了解到,书写代码中回遇到代码嵌套造成的回调地狱,以及代码执行中的错误捕获,还有异步代码执行时的先后顺序,Promise 这个Es6中新加入的内容,就是用来解决这一系列的问题。
~Promise 的基本用法
~~创建promise对象
new Promise((resolve,rejeck)=>{
}) //构造函数创建一个新的 Promise对象
~~promise状态
promise有两个参数,resolve和reject,分别对应成功响应与失败响应,当没有抛出失败或成功是,这个promise对象就在pending等待状态
console.log(new Promise((res,rej)=>{}))

console.log(new Promise((res,rej)=>{res(1)}))

console.log(new Promise((res,rej)=>{rej(1)}))

~~成功与错误捕获
当Promise对象状态发生改变,就由then或catch捕获
new Promise((res,rej)=>{
res('成功')
})
.then((res)=>{
console.log(res) //'成功' then第一个参数捕获成功
})
new Promise((res,rej)=>{
rej('失败')
})
.then((res)=>{
console.log(res)
})
.catch((error)=>{
console.log(error) //'失败' 利用catch捕获失败
})
new Promise((res,rej)=>{
rej('失败')
})
.then((res)=>{
console.log(res)
},error=>{
console.log(error) //'失败' 利用then第二个参数捕获失败
})
~~then()执行顺序
上面提到then中的代码时等待promise对象做出响应后才开始执行,表明它不是同步代码,事实也确实如此,then放在微任务队列等待执行,等待本轮主线程执行完毕后执行。具体可以看之前的 工作笔记 JS任务机制
~~then的链式调用
then中的代码都是微任务,在微任务队列中他们也按照顺序执行,可以理解为某种的同步,
two new Promise((res)=>{
res(1)
})
.then((res)=>{
console.log('one')
})
.then(()=>{
console.log('two')
})
//先输出one后输出two
new Promise((res)=>{
res(1)
})
.then((res)=>{
for(let i=0;i<9999;i++){
for(let x=0;i<9999;x++){
console.log(1)
}
}
})
.then(()=>{
console.log('two')
})
//先输出1后输出two
new Promise((res)=>{
res(1)
})
.then((res)=>{
setTimeout(()=>{
console.log(1)
},0)
})
.then(()=>{
console.log('two')
})
//先输出two后输出1
~~Promise链式调用
let promise=new Promise((res,rej)=>{
res('成功')
})
new Promise((res)=>{
res(promise)
})
.then((res)=>{
console.log(res) //'成功'
})
Promise改变状态是其他Promise时,执行then时需要等待父级的响应
let promise=new Promise((res,rej)=>{
setTimeout(()=>{
console.log(1)
},0)
res('成功')
})
new Promise((res)=>{
console.log(2)
res(promise)
})
.then((res)=>{
console.log(res)
})
// 2 成功 1
2是同步代码
成功是微任务
1是宏任务
输出的then的成功,需要父级promise的响应
~~Promise状态不可逆
在promise中执行到一个状态后,then的代码已经放在微任务队列中了,这个promise对象的状态永久的改变了并且无法改变。

~~每个then都是一个Promise




~~Prmise自己的then和链式的then哪个先触发?
一句话里面的then就是外面then的处理,并会被外面捕获


~~Promise任务队列
所谓任务队列就是一个个排序,依次执行。通过上面的学习,我们可以通过then来进行队列执行。但需要注意的是,每个then都需要返回一个Promise而且它的状态需要改变


通过上面的学习,我们可以改写为这样

如果我们请求两个彼此依赖接口实现队列,我们可以这样写
function p(arr){
let promise=Promise.resolve()
arr.map(item=>{
promise=promise.then(()=>{
return item()
})
})
}
function p1(){
return new Promise(res=>{
setTimeout(()=>{
res()
console.log(1)
},1000)
})
}
function p2(){
return new Promise(res=>{
setTimeout(()=>{
res()
console.log(2)
},1000)
})
}
p([p1,p2])

换成reduce更简洁一些
function p(arr){
arr.reduce((promise,item)=>{
return promise.then(res=>{
return item()
})
},Promise.resolve())
}
function p1(){
return new Promise(res=>{
setTimeout(()=>{
console.log(1)
res()
},1000)
})
}
function p2(){
return new Promise(res=>{
setTimeout(()=>{
console.log(2)
res()
},1000)
})
}
p([p1,p2])
模拟工作环境



~~catch() 与 then()捕获错误的区别
一句话处理自身的Promise的用then第二个参数,链式调用底部用catch兜底
~~finally()
不论Promise成功还是失败都会执行finally,和then相同在Promise后面调用.
应用场景如请求一个接口,不论成功失败最后都要取消掉loding动画我们就可以放在finally里
~~Promise封装异步加载图片
上文我们用回调函数创建了的加载图片,我们学习了Promise可以改造一下
function image(url){
return new Promise((res,rej)=>{
let img=new Image()
img.src=url
img.onload=()=>{
res(img)
}
img.onerror=rej
document.body.appendChild(img)
})
}
image('https://i2.hdslb.com/bfs/face/7e998fc9ba59e2e79a39bfdb620b5cdbb7ea04e3.png@86w_86h.webp').then(
(res)=>{
let img=res
img.style.border='10px solid red'
}
)
~~Promise封装定时器
function timeOut(time=1000){
return new Promise((res,rej)=>setTimeout(()=>{res()},time))
}
timeOut(2000).then(
()=>{
console.log(123)
return timeOut(1000)
}
)
.then(()=>{
console.log(456)
})
~~使用Promise异步改变样式
.div{
width: 200px;
height: 200px;
background: red;
position: absolute;
}
let div=document.querySelector('.div')
function interval(time=10,call){
return new Promise((res)=>{
let id=setInterval(()=>{
call(id,res)
},time)
})
}
interval(50,(id,res)=>{
let left=parseInt(window.getComputedStyle(div).left)
div.style.left=left + 10 + 'px'
if(left>=300){
clearInterval(id)
res(div)
}
})
.then((div)=>{
return interval(50,(id,res)=>{
let width=parseInt(window.getComputedStyle(div).width)
div.style.width=width - 10 + 'px'
if(width<30){
clearInterval(id)
res(div)
}
})
})
.then((res)=>{
div.style.background='#333'
})
尝试一下不使用Promise而是使用回调函数多层嵌套,回变成什么样子
~~all()
等待多个异步执行,后返回结果。最常用的场景app下来刷新,可能要重新请求多个接口,都这些异步接口都请求完毕后,结束lodding动画
let p1=new Promise((res,rej)=>{
setTimeout(()=>{
res(1)
},1000)
})
let p2=new Promise((res)=>{
setTimeout(()=>{
res(2)
},2000)
})
let all=Promise.all([p1,p2])
all.then(
(res)=>{
console.log(res,11)
}
)

all捕获错误

let p1=new Promise((res,rej)=>{
setTimeout(()=>{
rej(1) //err
},1000)
})
let p2=new Promise((res)=>{
setTimeout(()=>{
res(2)
},2000)
})
let all=Promise.all([p1,p2])
all.then(
(res)=>{
console.log(res,11)
}
,
(err)=>{
console.log(err,22)
}
)

~~allSettled()
与all的区别是,all捕获到一个错误,就会被捕捉,而且成功的也不会被resolve捕捉,就像是一个Promise一样状态被永远不可逆改变为失败状态一样。而allSettled则是不管是否有错误,都会走resolve不会被catch捕捉,在resolve中显示成功失败的数组。
let p1=new Promise((res,rej)=>{
setTimeout(()=>{
rej(1) //err
},1000)
})
let p2=new Promise((res)=>{
setTimeout(()=>{
res(2)
},2000)
})
let all=Promise.allSettled([p1,p2])
all.then(
(res)=>{
console.log(res,11)
}
,
(err)=>{
console.log(err,22)
}
)

~~race()
race和他的翻译一样赛马,放入多个Promsie,最先执行的完成的返回,成功res失败被catch捕捉
let p1=new Promise((res,rej)=>{
setTimeout(()=>{
rej(1) //err
},1000)
})
let p2=new Promise((res)=>{
setTimeout(()=>{
res(2)
},2000)
})
let all=Promise.race([p1,p2])
all.then(
(res)=>{
console.log(res,11)
}
,
(err)=>{
console.log(err,22) //1 22
}
)
async/await
async/await是Es2017加入的是Prmise的语法糖
~~async
在函数前使用,将函数返回为一个Prmise
async function p1(){
return 1
}
console.log(p1())
p1()
.then((res)=>{
console.log(res)
})

let p1 =new Promise((res)=>{
res(1)
})
console.log(p1)

~~await
await等待Promise返回结果
简单的来说 await就是用替代then链式调用的。是得可以更加优雅的书写
如果有多个异步操作需要排列执行,await配合async就非常适合
async function p(){
await time()
await time(3000)
await time(2000)
}
function time(Time=4000){
return new Promise(res=>{
setTimeout(()=>{
res(Time)
console.log(Time)
},Time)
})
}
p()

~~async/awite的错误机制
当多个awite在执行是,有一个执行错误,后续的awite将不再执行
async function p(){
let p1=await time()
console.log(p1)
let p2=await time(2000)
console.log(p2)
let p3=await time(3000)
console.log(p3)
}
function time(Time=1000){
return new Promise((res,rej)=>{
setTimeout(()=>{
if(Time==2000){
rej('err')
}
res(Time)
},Time)
})
}
p()

这是很常见的情况,请求三个接口,每个都彼此依赖,一个出现错误,下面的都执行不下去。通过上文我们可以很好的用then和catch的捕获机制处理。而用async/awite时我们依然可以捕获


