Promise.all为啥在单个promise执行了reject之后依然走了.then的逻辑?

291 阅读3分钟

在一个平平无奇的上午,测试突然反馈测得好好的,弹窗就打不开了,刷新完页面就好了。啥?这是发生了什么?我的弹窗去哪儿了呢?由于测试反馈出现弹窗打不开的中途有人部署了前端代码,我一开始怀疑难道是静态资源出了问题?于是开启漫长的找bug之旅...

1.不是静态资源的问题

看了下弹窗的引入和调用逻辑,并没有异步加载,所以排除静态资源没正确加载,那会是点击按钮打开弹窗的逻辑有问题?

2.弹窗打开逻辑

弹窗打开逻辑如下:一个点击按钮调用两个接口,接口调用成功,打开弹窗

function promise1() {
    return this.$http.post('url1', {})
    .then(() => {
        // 初始化弹窗展示需要的参数
        this.dialogInfo = xxx
    })
    .catch(() => {})
}
function promise2() {
    return this.$http.post('url2', {})
    .then(() => {})
}

function handleClick() {
    Promise.all([promise1, promise2])
      .then(() => {
        this.showDialog = true;
      })
      .catch((err) => {
        console.log(err)
      })
}

如上面伪代码,简单几行,也没看出什么问题。想着先复现一下?点了几次,未果。。。

3. 问题复现出来了!!!

正当我一筹莫展的时候,同事大佬喊我,复现出来了!!!

slow 3G 下,他复现了我的bug,此处为大佬疯狂打call!!!

经排查,我们发现了一个问题:在vue-devtools中,showDialog已经变成了true,但是弹窗展示需要的参数dialogInfo无值。所以我们猜测接口url1发生过报错,并且全局给showDialog的赋值语句只有一处,代码不会骗人,毫无疑问,程序一定是执行了Promise.all().then,不是说其中一个Promise发生了reject,Promise.all都会触发catch吗?这是为什么呢?此刻,人有点麻了...

4. 试下我们的猜想

接口报错,那可以让它超时,于是给了个很慢的下载速度,两个接口都超时了,发现showDialog为false,赋值逻辑并没有触发...难道只是第一个接口的锅?于是注释掉promise2,仅留下promise1再次尝试

果然,在promise1超时后,问题复现了,showDialog为true,且由于dialogInfo没有正确的值,控制台出现了弹窗的报错...这是为什么呢?

5. 难道是.catch的锅?

来做个尝试

const promise1 = new Promise((resolve, reject) => {
    resolve('success')
}).then((res) => {
    return res
})
const promise2 = new Promise((resolve, reject) => {
    reject('err')
}.then((res) => {
    return res
})
Promise.all([promise1, promise2])
    .then(res => {
        console.log('promise-all-success:' + res)
    })
    .catch(err => {
        console.log('promise-all-err:' + err)
    })

// 输出:promise-all-err:err
const promise1 = new Promise((resolve, reject) => {
    resolve('success')
}).then((res) => {
    return res
})
const promise2 = new Promise((resolve, reject) => {
    reject('err')
}).then((res) => {
    return res
}).catch((err) => {
    return err
})
Promise.all([promise1, promise2])
    .then(res => {
        console.log('promise-all-success:' + res)
    })
    .catch(err => {
        console.log('promise-all-err:' + err)
    })

// 输出:promise-all-success:success,err
const promise1 = new Promise((resolve, reject) => {
    resolve('success')
}).then((res) => {
    return res
})
const promise2 = new Promise((resolve, reject) => {
    reject('err')
}).then((res) => {
    return res
}, (err) => {
    return err
})
Promise.all([promise1, promise2])
    .then(res => {
        console.log('promise-all-success:' + res)
    })
    .catch(err => {
        console.log('promise-all-err:' + err)
    })
    
// 输出:promise-all-success:success,err

如上尝试,我们发现,在单个Promise中处理了错误,Promise.all将不会再次走catch的逻辑

最后再贴一段通义灵码给出的解释:

image.png

附上Promise.all的官方文档: developer.mozilla.org/zh-CN/docs/…