使用forEach来输出延时的Promise会有问题,如何顺序输出?

这篇博客详细探讨了JavaScript中Promise的使用,特别是在异步操作中的顺序输出问题。通过实例解释了forEach方法在处理异步操作时的并行执行特性,以及如何利用async/await和递归或reduce方法实现每秒输出一个结果。文章还介绍了手动调整Promise链以确保顺序执行的方法,帮助读者深入理解Promise的工作原理。
摘要由CSDN通过智能技术生成

题目:

1、题目输出的结果是什么?

2、如果想要每秒输出一个结果,可以怎么改造?(注意不可修改square方法)

    const list = [1,2,3]
    const square = (num)=>{
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                resolve(num*num)
            }, 1000);
        })
    }
    const test = function(){
        list.forEach(async (item)=>{
            const res = await square(item)
            console.log(res)
        })
    }
    test()

先说第一问的答案,是等待一秒,然后一起输出1、4、9。而并不是像大多数人感觉的那样,一秒钟输出一个。这是因为forEach的执行规则决定的,forEach里面是有块级作用域的,每个item执行时互相不受影响。首先拿到3个pending的promise对象,然后分别.then注册成功的回调,一秒后p1、p2、p3状态变为fulfilled,最后从上到下依次打印结果,3块代码会同时执行。如下方代码所示:

    {
        const p1 = square(1)
        p1.then(res =>{console.log(res)}) // 1
    }
    {
        const p2 = square(2)
        p2.then(res =>{console.log(res)}) // 4
    }
    {
        const p3 = square(3)
        p3.then(res =>{console.log(res)}) // 9
    }

forEach中没办法实现依次调用,我们可以用普通的for循环或者for of循环来实现顺序输出。

有迭代器的对象可以使用for of,直接拿到的val就是对应的值,而不用像普通for循环那样根据索引取值。

    async function test1(){
        for(let i=0;i<list.length;i++){
            const res = await square(list[i])
            console.log(res)
        }
    }
    test1()

    async function test2(){
        for(let val of list){
            const res = await square(val)
            console.log(res)
        }
    }
    test2()

改进的问题就这么解决了,但是并没有用到太多Promise的知识,于是我复习了一下Promise。先用手动挡找找感觉:在p1成功的回调里拿到p2,在p2的回调里拿到p3,这样确保顺序执行。

    const p1 = square(1)
    p1.then(res1=>{
        console.log(res1)
        const p2 = square(2)
        p2.then(res2=>{
            console.log(res2)
            const p3 = square(3)
            p3.then(res3=>{
                console.log(res3)
            })
        })
    })

上面代码随着数组不断增长,缩进会越来越离谱,显然是不好的。then()方法的返回值是一个Promise对象,对象的状态根据then()方法内部return的返回值决定,如果return了Promise对象,then()方法就返回一个状态和值都与之相同的Promise对象。如果内部return的不是Promise对象,那就会无脑返回一个成功的Promise对象,值为return后的值。上面then()方法内部没有写return,默认返回undefined,所以每个then()方法的值都是一个值为undefined的成功态Promise对象。

    const p1 = square(1)
    p1.then(res=>{
        console.log(res)
        return square(2)
    }).then(res=>{
        console.log(res)
        return square(3)
    }).then(res=>{
        console.log(res)
    })

我们完全可以在上一个Promise状态变为成功,执行then()方法的内部,把下一个Promise对象给return出去。这样就可以解决代码不断向前缩进的问题。手动挡热身完毕,接下来就该写方法了。

    const handleSquare = (arr)=>{
        if(arr.length === 0 ) return
        square(arr[0]).then(res=>{
            console.log(res)
            handleSquare(arr.slice(1)) 
        })
    }
    handleSquare(list)

slice方法并不会修改原数组,并且返回值是一个新数组,所以放心大胆地传list吧。handleSquare()每次传入的数组都会删除第一位,全删掉以后就会结束递归。除此之外用reduce()也可以实现类似效果。

    list.reduce((pre,cur)=>{
        return pre.then((res)=>{
            square(cur).then(res=>{console.log(res)})
            return square(cur)
        })
    },Promise.resolve())

这里reduce的初始值仅仅是为了启动第一次的Promise(),初始值一定要是Promise对象。

首次的pre是设置的成功态Promise初始值,它的then()返回了第一项的square(1),并且在上面定义了square(1)成功的回调,回调只是打印了值。

第二次pre是刚刚返回的square(1),一秒钟后变成成功态,执行上面定义的打印操作。还要执行当前pre的一个then()成功的回调:注册square(2)的then(),并且return square(2)。

加上辅助打印验证一下:

    list.reduce((pre,cur)=>{
        return pre.then((res)=>{
            console.log('pre',res)
            square(cur).then(res=>{console.log('cur',res)})
            return square(cur)
        })
    },Promise.resolve())

一开始瞬间打印pre undefined,因为我的初始值Promise里并没有写内容。给square(1)注册打印的回调,然后return square(1)。

过一秒后打印:cur 1 pre 1,现在的pre是square(1),先执行上一轮先注册的回调(打印),然后再执行当前pre的回调(给square(2)注册打印的回调,return square(2))

又过一秒后打印:cur 4 pre 4,现在的pre是square(2),先执行上一轮先注册的回调(打印),然后再执行当前pre的回调(给square(3)注册打印的回调,return square(3)),此时cur为3,reduce()已经结束。

又又过一秒后打印:cur 9,由于reduce()在上一步结束了,pre并不会成为square(3)。最后仅是执行上一轮注册的回调(打印)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值