因祸得福的一道promise面试题

作者 likaiqiang 日期 2019-05-12
因祸得福的一道promise面试题

面试题

function execute(tasks:Promise[]):Promise<any[]>{

}

题目的本意是 execute函数的参数是个promise数组([promise1,promise2]),返回值是一个promise,这个promise成功回调的参数是任务的执行结果,顺序为执行顺序。

我理解成了execute函数传入一个promise数组,返回一个执行结果的数组,即[1,2,null]。。。

假如是我理解的那样,就会面临一个问题,异步的promise怎么把结果return给外层的同步函数,不使用Generator、async,JavaScript根本就办不到(反正我不行,想的我怀疑人生)。

不过也算是因祸得福,为了这个面试题,我把promise重新看了一遍。

promise

基本用法

首先Promise是个构造函数,参数是一个叫做execute(执行器)的函数,execute函数接受两个参数resolve与reject,如果resolve被调用,表示该promise的状态为成功,如果reject被调用,表示该promise的状态为失败。

promise对象有一个then方法,参数是两个函数,分别表示该promise成功的回调与失败的回调。

function run(){
// run函数是一个耗时很长的异步操作
}

使用promise进行封装

function run(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve('success')
},10000)
})
}
run().then(function(data){
console.log(data) // success
},function(error){

})

promise的链式调用

一个promise可以无限的then下去,直到这个promise结束,promise A+ 规范里面要求每个then方法必须返回一个promise对象,但是实际测试结果来看,返回值是什么都可以,甚至不返回也可以,貌似then方法的实现代码里做了处理。

promise之所以能链式调用,原因在于每个then方法都会返回一个新的promise(注意是新的promise)

function run(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve('success')
},100)
})
}
run().then(function(data){
console.log(data)
},function(){

}).then(function(data){
console.log(1,data)
})

输出 success 1 undefined

run().then(function(data){
console.log(data)
return 2
},function(){

}).then(function(data){
console.log(1,data)
})

输出 success 1 2

run().then(function(data){
console.log(data)
return Promise.resolve(2)
},function(){

}).then(function(data){
console.log(1,data)
})

输出 success 1 2

还有一点需要注意,如果前一个then的回调(不管是成功还是失败)返回了一个失败的promise,会进入后一个then的失败回调。来一个经典代码。

Promise.resolve().then(function(){
console.log('success 1')
return Promise.reject()
},function(){
console.log('error 1')
}).then(function(){
console.log('success 2')
},function(){
console.log('error 2')
})

输出 success1 error2

reduce

废话不多说MDN文档很详细

最后实现

言归正传,利用reduce + promise 实现面试题的效果

function exectue(tasks){
var result = []
return tasks.reduce((promise,item,index)=>{
return item.then(res=>{
result.push(res)
if(index == tasks.length-1) return result
else return res
})
},Promise.resolve())
}
exectue([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]).then(data=>{
console.log(data) //1,2,3
})

原理:利用promise的then方法会返回新的promise这个特性,再加上reduce,相当于顺序执行promise,用result存储结果,最后一个then返回result。

PS:以上方法只对同步promise生效。要想异步promise也串行执行,还是要感谢思否

2020年5月17日更新:

function createPromise(time=0){
return function(){
return new Promise(resolve=>{
setTimeout(()=>{
console.log(time)
resolve(time)
},time)
})
}
}

function exectue(tasks){
if(!Array.isArray(tasks)) return
return tasks.reduce((acc,promise,index)=>{
return acc.then(()=>{
if(typeof promise != 'function') {
promise = ()=>promise()
}
return promise()
})
},Promise.resolve())
}

var tasks = [
createPromise(3000),
createPromise(2000),
createPromise(1000)
]

exectue(tasks).then(()=>{
console.log('done')
})

很聪明的一种写法。每个Promise实例的then方法都会返回一个新的Promise实例。如果我们在then里面再return一个promise,然后这个then方法的返回值(一个新的promise)的状态就会与我们手动return的promise保持同步。这就是串行promise的精髓。

上面的例子用普通的写法就是这样:

Promise.resolve().then(()=>{
return new Promise(resolve=>{
setTimeout(()=>{
console.log(3000)
resolve(3000)
},3000)
})
}).then(()=>{
return new Promise(resolve=>{
setTimeout(()=>{
console.log(2000)
resolve(2000)
},2000)
})
}).then(()=>{
return new Promise(resolve=>{
setTimeout(()=>{
console.log(1000)
resolve(1000)
},1000)
})
}).then(()=>{
console.log('done')
})

但是作为一个有追求的程序员,是不是应该封装一个方法。这里要注意两点:

  1. exectue的参数必须是一个函数,不然在传参的那一刻promise就执行了,根本不可能串行。
  2. reduce是同步的,而promise是异步的。

一年了,才悟到精髓。。。