用JavaScript Promise迷你书解析一道promise必考面试题(文末送书)

492 阅读3分钟

  本文通过讲解JavaScript Promise迷你书的前两章有关Promise的关键知识来解析一道常考的promise面试题-手动实现Promise.all。
第一章主要介绍了如何编写promise代码:   第二章为promise实战延伸,只提取了3个常用的知识点:
可能光是看知识点不能完全理解,下面会结合手动实现Promise.all来应用上面的知识点。首先看一下promise.all的使用示例:

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    console.log(results);  // [1, 2, 3]
});

  Promise.all接收一个promise数组,返回一个promise实例,then回调函数参数为promise满足返回值的数组,与Promise.all接收的promise实例相对应。如果有一个promise变成reject状态,就执行catch方法:

var p1 = Promise.resolve(1),
    p2 = Promise.reject(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    //then方法不会被执行
    console.log(results);
}).catch(function (e){
    //catch方法将会被执行,输出结果为:2
    console.log(2);
});

  Promise.all返回的是一个promise实例,所以我们手写的过程中也要返回一个promise,利用promise迷你书中实例化Promise的方法:

Promise.myall = function(promises){
	return new Promise(fn)
}

  接下来我们再来处理promise数组promises,这里我们应该如何来获取promise实例变成fullfilled状态后的返回值呢?我们可以通过promise实例的then方法注册满足状态的回调函数,来获取满足状态的返回值。我们可以通过for循环的方式通过执行promise.then的方式来等待所有promise实例变化成resolve状态。此处还用到了闭包的知识点,在for循环中,如果i较小的promise居后返回了,在for的循环作用域中仍然可以记忆当时的i序号,返回主体为promise,结果数组变量在这个promise的构造过程中声明就好:

Promise.myall = function(promises){
	return new Promise(function(resolve, reject){
    	let promiseNum = promises.length;
    	let resolvedValues = [];
    	for(let i = 0; i < promiseNum; i++){
        	promises[i].then(function(res){
            	resolvedValues[i] = res;
            })
        }
    })
}

  接下来我们需要判断promise全部返回的时机,可以在for循环中通过结果i序号与输入promises数组长度对比来判断么?不可以,因为这样只要for循环完就会使我们的myall返回的promise变成fullfilled状态,不管所有异步promise是否都已经返回。所以我们可以把判断的逻辑放在每一个promise状态变成fullfilled的回调函数中来进行判断,这样就可以与promise真正变化状态的时机联系起来。但是如果还是判断序号i与promises长度还是不可以,因为如果最后一个promise先返回,那么依然会存在误判。所以我们通过序号i来判断并不准确,最准确的判断条件还是需要用在每一个promise状态变化之后的回调函数中来计数才比较安全:

Promise.myall = function(promises){
	return new Promise(function(resolve, reject){
    	let promiseNum = promises.length;
    	let resolvedValues = [];
        let resolvedCounter = 0;
    	for(let i = 0; i < promiseNum; i++){
        	promises[i].then(function(res){
            	resolvedCounter++;
            	resolvedValues[i] = res;
                if (resolvedCounter == promiseNum) {
                  	return resolve(resolvedValues);
                }
            })
        }
    })
}

  下面我们还要考虑如果有一个promise变成rejected的状态,我们需要在promise构造函数的函数参数中调用reject的,很简单只需要在then的第二个参数来加上执行reject函数的逻辑即可:

Promise.myall = function(promises){
	return new Promise(function(resolve, reject){
    	let promiseNum = promises.length;
    	let resolvedValues = [];
        let resolvedCounter = 0;
    	for(let i = 0; i < promiseNum; i++){
        	promises[i].then(function(res){
            	resolvedCounter++;
            	resolvedValues[i] = res;
                if (resolvedCounter == promiseNum) {
                  	return resolve(resolvedValues);
                }
            }, function(reason){
            	return reject(reason);
            })
        }
    })
}

  至此一个基本的Promise.myall就实现好了,但是有的面试管这时还会来一个附加题:Promise.all是所有的promise同时开始执行,但是如果想让上一个promise执行完再执行下一个promise又该如何实现?
可能最先想到的是用await对上面的结果进行修改。但是我们也可以通过promise chain的方式来思考,下面是promise迷你书中介绍的一种方法:

function main(){
	function recordValue(results, value){
    	results.push(value);
        return results;
    }
    var pushValue = recordValue.bind(null, []);
    var tasks = [promise1, promise2];
    var promise = Promise.resolve();
    for(var i = 0; i < tasks.length; i++){
    	var task = function(){
        	tasks[i].then((res)=>{
            	return res;
            });
        }
        promise = promise.then(task).then(pushValue);
    }
    return promise;
}

main().then(function(value){
	console.log(value);
}).catch(function(error){
	console.error(error);
})

  可以简单分析一下,摘抄下来的时候有一些改动。首先再for循环前面通过Promise.resolve()来初始化一个fullfilled状态的promise实例,将需要迭代的每个promise放到了一个函数中,即为上一个promisefullfilled状态的回调函数,在回调函数中返回结果,经过promise迷你书中所述的then中回调函数返回值会经过Promise.resolve(返回值)包装,所以链式调用then时,会把返回的结果直接传入pushValue中,而pushValue处理完了上个结果后,会返回一个新的promise,下次迭代会基于这个新的已经fullfilled的promise直接执行满足状态的回调,最后返回的是所有promise数组返回的结果。

参考: 《JavaScript Promise 迷你书》

juejin.cn/post/684490…

您是否也面临不知道自己在行业内技术水平如何,遇到的技能点由于长期没有再接触过容易忘记的问题呢?欢迎访问刷题小程序来解决上述两点问题,每月刷题最多的同学可以任选100元以内图书一本哦~