promise经典面试题

上期讲了promise基本概念和用法,今天结合上期的内容,讲解几道经典的相关面试题。javascript

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 对象形式使用。html

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里面的函数不会再执行。html5

题目二

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,successjava

题目三

在浏览器上,下面的程序会一次输出哪些内容?node

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。宏任务会阻塞浏览器的渲染进程,微任务会在宏任务结束后当即执行,在渲染以前。web

回到题目,结果为:'1,2,10,5,6,8,9,3'。你答对了吗?若是对了,那你基本理解了事件队列,微任务,宏任务了。面试

第一步:执行宏任务,结合规则一,输出:1,2,10。这时候事件循环里面有异步任务timeOut1,timeOut2,p2.then,p1.thenchrome

第二步:宏任务执行完后Event Loop会去任务队列取异步任务,微任务会优先执行,这时候会前后执行p2.then,p1.then,打印5,6。promise

第三步:微任务执行完了,开始宏任务,因为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队列循环加入,直到达到限制,等待racerace后又加入一个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 promisethen方法,实现上比较巧妙。

总结

promise对象在JavaScript中的使用相对复杂,由于写法多变,并且灵活,提供的方法又比较复杂难懂,在ES6普及的今天,使用范围也广,因此会高频的出如今面试过程当中。

相关阅读:

Promise讲解

前端异步是什么?哪些状况下会发生异步?

知道html5 Web Worker标准吗?能实现JavaScript的多线程?

学习如逆水行舟,不进则退,前端技术飞速发展,若是天天不坚持学习,就会跟不上,我会陪着你们,天天坚持推送博文,跟你们一同进步,但愿你们能关注我,第一时间收到最新文章。

我的公众号:长按保存关注

相关文章
相关标签/搜索