题目:
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)。最后仅是执行上一轮注册的回调(打印)。