promise经典面试题

上期讲了promise基本概念和用法,今天结合上期的内容,讲解几道经典的相关面试题。 promise基本规则: 1. 首先Promise构造函数会立即执行,而Promise.then()内部的代码在当次事件循环的结尾立即执行(微任务)。 2. promise的状态一旦由等待pending变为成功fulfilled或者失败rejected。那么当前promise被标记为完成,后面则不会再次改变该状态。 3. resolve函数和reject函数都将当前Promise状态改为完成,并将异步结果,或者错误结果当做参数返回。 4. Promise.resolve(value) 返回一个状态由给定 value 决定的 Promise 对象。如果该值是 thenable(即,带有 then 方法的对象),返回的 Promise 对象的最终状态由 then 方法执行决定;否则的话(该 value 为空,基本类型或者不带 then 方法的对象),返回的 Promise 对象状态为 fulfilled,并且将该 value 传递给对应的 then 方法。通常而言,如果你不知道一个值是否是 Promise 对象,使用 Promise.resolve(value) 来返回一个 Promise 对象,这样就能将该 value 以 Promise 对象形式使用。 5. Promise.all(iterable)/Promise.race(iterable) 简单理解,这2个函数,是将接收到的promise列表的结果返回,区别是,all是等待所有的promise都触发成功了,才会返回,而arce有一个成功了就会返回结果。其中任何一个promise执行失败了,都会直接返回失败的结果。 6. promise对象的构造函数只会调用一次,then方法和catch方法都能多次调用,但一旦有了确定的结果,再次调用就会直接返回结果。 开始答题 题目一 const promise = new Promise((resolve, reject) => { console.log(1); resolve(); console.log(2); reject('error'); }) promise.then(() => { console.log(3); }).catch(e => console.log(e)) console.log(4); 可以看:规则一,promise构造函数的代码会立即执行,then或者reject里面的代码会放入异步微任务队列,在宏任务结束后会立即执行。规则二:promise的状态一旦变更为成功或者失败,则不会再次改变,所以执行结果为:1,2,4,3。而catch里面的函数不会再执行。 题目二 const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('once') resolve('success') }, 1000) }) promise.then((res) => { console.log(res) }) promise.then((res) => { console.log(res) }) 根据规则6,promise的构造函数只会执行一次,而then方法可以多次调用,但是第二次是直接返回结果,不会有异步等待的时间,所以执行结果是: 过一秒打印:once,success,success。 题目三 在浏览器上,下面的程序会一次输出哪些内容? const p1 = () => (new Promise((resolve, reject) => { console.log(1); let p2 = new Promise((resolve, reject) => { console.log(2); const timeOut1 = setTimeout(() => { console.log(3); resolve(4); }, 0) resolve(5); }); resolve(6); p2.then((arg) => { console.log(arg); }); })); const timeOut2 = setTimeout(() => { console.log(8); const p3 = new Promise(reject => { reject(9); }).then(res => { console.log(res) }) }, 0) p1().then((arg) => { console.log(arg); }); console.log(10); 事件循环:javascript的执行规则里面有个事件循环Event Loot的规则,在事件循环中,异步事件会放到异步队列里面,但是异步队列里面又分为宏任务和微任务,浏览器端的宏任务一般有:script标签,setTimeout,setInterval,setImmediate,requestAnimationFrame。微任务有:MutationObserver,Promise.then catch finally。宏任务会阻塞浏览器的渲染进程,微任务会在宏任务结束后立即执行,在渲染之前。 回到题目,结果为:'1,2,10,5,6,8,9,3'。你答对了吗?如果对了,那你基本理解了事件队列,微任务,宏任务了。 第一步:执行宏任务,结合规则一,输出:1,2,10。这时候事件循环里面有异步任务timeOut1,timeOut2,p2.then,p1.then。 第二步:宏任务执行完后Event Loop会去任务队列取异步任务,微任务会优先执行,这时候会先后执行p2.then,p1.then,打印5,6。 第三步:微任务执行完了,开始宏任务,由于2个settimeout等待时间一样,所以会执行先进入异步队列的timeOut2,先后打印:8。执行宏任务的过程中,p3.then微任务进入了队列,宏任务执行完毕会执行微任务,输出:9。之后执行timeOut1,输出:3。 第四步:结合规则6,由于p2这个Promise对象的执行结果已经确定,所以4不会被打印。 注:在node.js上输出结果并不是这样的,因为node.js的事件循环跟浏览器端的有区别。 题目四 在不使用async/await的情况下,顺序执行一组异步代码函数,并输出最后的结果。 在上篇文章中,已经讲到过,利用promise.resolve结合reduce能顺序执行一组异步函数。 const applyAsync = (acc,val) => acc.then(val); const composeAsync = (...dd) => x => dd.reduce(applyAsync, Promise.resolve(x)); const transformData = composeAsync(funca, funcb, funcc, funcd); transformData(1).then(result => console.log(result,'last result')).catch(e => console.log(e)); 以上代码可以封装成工具来使用,利用的是规则4,promise.resolve函数的特点,其中dd可以是一组同步函数,也可以是异步函数。最后的结果在result里面,异常信息能在最后捕获。想看更具体的可以查看这篇文章: promise讲解 题目五 顺序加载10张图片,图片地址已知,但是同时最多加载3张图片,要求用promise实现。 const baseUrl = 'http://img.aizhifou.cn/'; const urls = ['1.png', '2.png', '3.png', '4.png', '5.png','6.png', '7.png', '8.png', '9.png', '10.png']; const loadImg = function (url, i) { return new Promise((resolve, reject) => { try { // 加载一张图片 let image = new Image(); image.onload = function () { resolve(i) } image.onerror = function () { reject(i) }; image.src = baseUrl + url; } catch (e) { reject(i) } }) } function startLoadImage(urls, limits, endHandle) { // 当前存在的promise队列 let promiseMap = {}; // 当前索引对应的加载状态,无论成功,失败都会标记为true,格式: {0: true, 1: true, 2: true...} let loadIndexMap = {}; // 当前以及加载到的索引,方便找到下一个未加载的索引,为了节省性能,其实可以不要 let loadIndex = 0; const loadAImage = function () { // 所有的资源都进入了异步队列 if (Object.keys(loadIndexMap).length === urls.length) { // 所有的资源都加载完毕,或者进入加载状态,递归结束 const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, []) Promise.all(promiseList).then(res => { // 这里如果没有加载失败,就会在所有加载完毕后执行,如果其中某个错误了,这里的结果就不准确,不过这个不是题目要求的。 console.log('all'); endHandle && endHandle() }).catch((e) => { console.log('end:' + e); }) } else { // 遍历,知道里面有3个promise while (Object.keys(promiseMap).length < limits) { for (let i = loadIndex; i < urls.length; i++) { if (loadIndexMap[i] === undefined) { loadIndexMap[i] = false; promiseMap[i] = loadImg(urls[i], i); loadIndex = i; break; } } } // 获取当前正在进行的promise列表,利用reduce从promiseMap里面获取 const promiseList = Object.keys(promiseMap).reduce((arr, item) => {arr.push(promiseMap[item]); return arr}, []) Promise.race(promiseList).then((index) => { // 其中一张加载成功,删除当前promise,让PromiseList小于limit,开始递归,加载下一张 console.log('end:' + index); loadIndexMap[index] = true; delete promiseMap[index]; loadAImage(); }).catch(e => { // 加载失败也继续 console.log('end:' + e); loadIndexMap[e] = true; delete promiseMap[e]; loadAImage(); }) } } loadAImage() } startLoadImage(urls, 3) 将代码复制到chrome浏览器可以看到下面的运行结果: 1.png 可以看到,所有图片加载完成,在没有失败的情况下,打印出来all。 解析:根据规则5,Promise.race方法接受的参数中有一个promise对象返回结果了就会立即触发成功或者失败的函数。这里利用这个特性,先将promise队列循环加入,直到达到限制,等待race,race后又加入一个promise,利用递归一直循环这个过程,到最后用promise.all捕获剩下的图片加载。 题目六 写出下面函数的执行结果: Promise.resolve(1) .then(2) .then(Promise.resolve(3)) .then(console.log) 根据规则4,Promise.resolve(1)会返回一个promise对象并且会将1当做then的参数。而.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。所以最后会输出:1。 题目六 如何取消一个promise? 刚开始拿到这个题会觉得比较蒙,实际上,我们可以用Promise,race的特点,多个Promise有个状态变为完成,就会立马返回。 function wrap(p) { let obj = {}; let p1 = new Promise((resolve, reject) => { obj.resolve = resolve; obj.reject = reject; }); obj.promise = Promise.race([p1, p]); return obj; } let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 1000); }); let obj = wrap(promise); obj.promise.then(res => { console.log(res); }); // obj.resolve("请求被拦截了"); 一旦开发者在1秒内主动调用obj.resolve,那么obj.promise方法就会被替换成我们自己的方法,而不会执行let promise的then方法,实现上比较巧妙。 总结 promise对象在JavaScript中的使用相对复杂,因为写法多变,而且灵活,提供的方法又比较复杂难懂,在ES6普及的今天,使用范围也广,所以会高频的出现在面试过程中。 相关阅读: Promise讲解 前端异步是什么?哪些情况下会发生异步? 知道html5 Web Worker标准吗?能实现JavaScript的多线程? 学习如逆水行舟,不进则退,前端技术飞速发展,如果每天不坚持学习,就会跟不上,我会陪着大家,每天坚持推送博文,跟大家一同进步,希望大家能关注我,第一时间收到最新文章。 个人公众号: 长按保存关注

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):