一直以来,我都是使用promise
对接口调用进行处理,随着使用的场景逐渐复杂,发现自己对promise
的使用的理解并不到位,所以整理一篇笔记来做个归纳,加深自己的理解
一、promise 的异步执行
首先讲一下promise
的异步执行机制,想必大家都听说过:异步任务会放到任务队列中,等待同步任务执行完后再执行;并且你可以使用async
/await
来阻塞promise
异步代码,那么我们来看下面的一段代码
const promise = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
}
const func1 = async () => {
await promise()
console.log(1)
}
func1()
const func2 = async () => {
await promise()
console.log(2)
}
func2()
这段代码的 1 和 2 会同时输出,再来看下一段代码
const promise = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
}
const func1 = async () => {
await promise()
console.log(1)
await promise()
console.log(2)
}
func1()
此时,2 会比 1 晚一秒才输出,所以可以发现async
/await
仅在它们各自的作用域下会有阻塞的现象
其实async
/await
就是then
的语法糖,我们可以将上面的两段代码翻译一下:
// 第一段
promise().then(() => console.log(1))
promise().then(() => console.log(2))
// 第二段
promise().then(() => {
console.log(1)
promise().then(() => {
console.log(2)
})
})
这样其实就更加清晰了,第二段代码实际上就是一个promise
的嵌套,而第一段是并行的
二、promise 在循环中的串行执行
只有对promise
理解足够清晰,才能在promise
循环调用中正确判断是否会阻塞代码执行
先来看看下面这段代码,在这段代码中,1、2、3、4、5 会同时输出
const promise = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
}
const arr = [1, 2, 3, 4, 5]
let res = Promise.resolve()
arr.forEach(async (i) => {
res = await promise()
console.log(i)
})
这段代码其实就是上面第一点的第一段代码,forEach
只会同步地执行它的回调,回调里面的异步内容的作用域也仅限于这个回调函数,await
并不能阻塞等待这个函数执行完才执行下一个,而是被直接丢到任务队列
然后我们再来看一段代码,在这段代码中,每个数字的输出都会间隔 1 秒
const promise = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
}
const arr = [1, 2, 3, 4, 5]
let res = Promise.resolve()
arr.forEach((i) => {
res = res.then(async () => {
console.log(i)
await promise()
})
})
仔细想想,这一段其实就是在链式地调用then
,所以起到了阻塞的作用:
res = promise()
.then(() => { console.log(1); return promise() })
.then(() => { console.log(2); return promise() })
.then(() => { console.log(3); return promise() })
.then(() => { console.log(4); return promise() })
.then(() => { console.log(5); return promise() })
所以,总结一下,promise
的串行调用实际上的实现就是then
的链式调用
此外,还有一种利用reduce
的巧妙写法,这种其实和上面第一种写法是等价的:
arr.reduce((pre, item) => (
// 所有的逻辑都写在这个 then 里面
pre.then(async () => {
await promise()
console.log(item)
})
), Promise.resolve())
那么,在循环中使用await
来就无法实现实现串行执行吗,其实可以用while
循环来做:
const func1 = async () => {
let i = 0
while(i < arr.length) {
await promise()
console.log(arr[i])
i++
}
console.log("while循环结束")
}
func1()
可以发现每隔一秒才会有输出,并且最后一句结束输出是在while
循环完了之后才执行的
三、promise 在循环中的并行执行
第二点中的第一个例子就是常见的做法,在每个函数内使用await
,阻塞该函数内部的代码,而不会阻塞下一个循环的函数的代码
const promise = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, 1000)
})
}
const arr = [1, 2, 3, 4, 5]
let res = Promise.resolve()
arr.forEach(async (i) => {
res = await promise()
console.log(i)
})