# [JS] Promise 异步的并行与串行调用

一直以来，我都是使用`promise`对接口调用进行处理，随着使用的场景逐渐复杂，发现自己对`promise`的使用的理解并不到位，所以整理一篇笔记来做个归纳，加深自己的理解

## 一、promise 的异步执行

首先讲一下`promise`的异步执行机制，想必大家都听说过：异步任务会放到任务队列中，等待同步任务执行完后再执行；并且你可以使用`async`/`await`来阻塞`promise`异步代码，那么我们来看下面的一段代码

```js
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 会同时输出，再来看下一段代码

```js
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`的语法糖，我们可以将上面的两段代码翻译一下：

```js
// 第一段
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 会同时输出

```js
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 秒

```js
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`，所以起到了阻塞的作用：

```js
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`的巧妙写法，这种其实和上面第一种写法是等价的：

```js
arr.reduce((pre, item) => (
    // 所有的逻辑都写在这个 then 里面
    pre.then(async () => {
        await promise()
        console.log(item)
    })
), Promise.resolve())
```

那么，在循环中使用`await`来就无法实现实现串行执行吗，其实可以用`while`循环来做：

```js
const func1 = async () => {
    let i = 0
    while(i < arr.length) {
        await promise()
        console.log(arr[i])
        i++
    }
    console.log("while循环结束")
}
func1()
```

可以发现每隔一秒才会有输出，并且最后一句结束输出是在`while`循环完了之后才执行的

## 三、promise 在循环中的并行执行

第二点中的第一个例子就是常见的做法，在每个函数内使用`await`，阻塞该函数内部的代码，而不会阻塞下一个循环的函数的代码

```js
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)
})
```
