前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
在学习Promise相关题目之前,我们先做一些知识的回顾:JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。JS执行机制中有同步任务
和异步任务
,执行入队优先级是前者;异步任务又分为宏任务
和微任务
,执行入队优先级是后者。我们先看一下下面例子来充分理解一下JavaScript执行机制:(如代码所示。如图所示)
console.log(1) setTimeout(() => { console.log(2) }, 0) const promise = new Promise((resolve, reject) => { console.log(3); resolve(4) }) promise.then((res) => { console.log(res) }) // 1 3 4 2
- 同步任务:
只有前一个任务执行完毕,才能执行后一个任务
。还记得大学饭堂排队打饭例子吗,作为一个优秀的当代大学生,排队打饭遵循先来先打后来排队原则,只有前面那一个同学打完饭,你才能打。当然,你插队就另说😜。 - 异步任务:
由JavaScript 委托给宿主环境进行执行
。当异步任务执行完成后,会通知JavaScript 主线程执行异步任务的回调函数。还记得铁板烧吗,说实际的,铁板烧确实不错,细心的你有没有发现,老板里有多个锅,不可能只有一个锅,每一份铁板烧都需要时间,不然让顾客等待得花儿都谢了,你下次也不会来了,所以多个锅就代表多个任务,不需要等待一个锅烧完才去重新烧,也就是说不需要等待当前任务结束(这个任务没有那么快完成,未来某个时间点才结束,它就是异步任务),为节省时间或能耗,可以继续去执行其他任务。 - 宏任务:JavaScript自身发起。如setTimeout 、setInterval MessageChannel I/O、setImmediate(Node环境)、script(整体代码块)
- 微任务:是由宿主(浏览器、Node)发起的。MutationObserver(浏览器环境)、promise.[ then/catch/finally ]、事件队列 process.nextTick(Node环境)
- Promise:异步编程的一种解决方案,可以通俗把它当作一个容器,内部存储着某个未来才会结束的事件(通常是一个异步操作)的结果,从语法上来讲它就是一个对象,有两个特点:
有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
- 对象状态不受外界影响。
- 状态一旦改变就不会在变,也就是说任何时候Promise都只有一种状态。
话不多说,我们直接上题目实操吧,一步步理解!
题目1️⃣:Promise没有resolve前都是同步任务。 Promise是微任务,但是Promise构造器内有同步任务,js线程会先把同步任务执行完,再去执行resolve回调。
const promise = new Promise((resolve, reject) => { console.log(1); resolve(2) console.log(3); }); promise.then((res) => { console.log(res); }); console.log(4, promise);// 4 fulfilled // 1 3 4 2
题目2️⃣:当函数返回的是一个Promise实例。
const fn = () => (new Promise((resolve, reject) => { console.log(1); resolve(2) })) fn().then(res => { console.log(res) }) console.log(3) // 1 3 2
题目3️⃣:宏任务与微任务执行顺序。 定时器是宏任务,Promise是微任务,其他都是同步任务,
console.log(1) setTimeout(() => { console.log(2) }) Promise.resolve().then(() => { console.log(3) }) console.log(4) // 1 4 3 2
题目4️⃣:当微任务中嵌套宏任务、宏任务嵌套微任务、宏任务嵌套宏任务、微任务嵌套微任务时。
- 当微任务中嵌套宏任务时:由于构造器中除了resolve执行回调之外,还有其他同步任务、宏任务。
// 当微任务中嵌套宏任务 const promise = new Promise((resolve, reject) => { console.log(1); setTimeout(() => { console.log(2); resolve(3); console.log(4); }, 0); console.log(5); }); promise.then((res) => { console.log(res); }); console.log(6); // 1 5 6 2 4 3
- 当宏任务嵌套宏任务时:当发生嵌套任务时,优先处理同层级,因为程序是自上而下的,setTimeout是异步任务中的宏任务,同层级的比嵌套的先入队,所以先执行同层级。
// 宏任务嵌套宏任务 setTimeout(() => { console.log(1); setTimeout(() => { console.log(2) }, 0) }, 0) setTimeout(() => { console.log(3) }, 0) console.log(4) // 4 1 3 2
- 当宏任务嵌套微任务时:
// 宏任务嵌套微任务 setTimeout(() => { console.log(1); // 微任务 Promise.resolve().then(() => { console.log(2) }) }, 0) setTimeout(() => { console.log(3) }, 0) console.log(4) // 4 1 2 3
- 微任务嵌套微任务时:同层级微任务优先执行。因为任务队列先入队都是同级别的。
// 微任务链接微任务 Promise.resolve().then(() => { console.log(1) return 2; }).then((res) => { console.log(res); }) Promise.resolve().then(() => { console.log(3) }) // 1 3 2 // 微任务嵌套微任务 Promise.resolve().then(() => { console.log(1) Promise.resolve().then(() => { console.log(2) }) return 3 }).then((res) => { console.log(res); }) Promise.resolve().then(() => { console.log(4) }) // 1 4 2 3
题目5️⃣:结合微任务和宏任务,灵活理解Promise三种状态。从代码中我们可以看出例子中的单纯是同步任务有2 3 4,而宏任务中的同步任务是1,当执行程序中同步任务时,微任务还没有resolve回调函数,所以promise对象都是pending状态,由于抛出错误是微任务中的宏任务,所以优先执行,然后再执行全局的setTimeout,最后promise1是fufilled状态,promise2是rejected状态。
const promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("success"); console.log(1); }, 1000); console.log(2); }); const promise2 = promise1.then(() => { throw new Error("error!!!"); }); console.log(3, promise1);// pending console.log(4, promise2);// pending setTimeout(() => { console.log(5); console.log(6, promise1);// fufilled console.log(7, promise2);// rejected }, 2000); // 2 3 4 1 抛出error! 5 6 7
题目6️⃣:Promise中构造函数中的resolve或reject只有第一次执行有效。
const promise = new Promise((resolve, reject) => { reject("error"); resolve(1); }); promise.then(res => { console.log("then1: ", res); }).then(res => { console.log("then2: ", res); }).catch(err => { console.log("catch: ", err); }).then(res => { console.log("then3: ", res); }) // catch: error // then3: undefined
题目7️⃣:Promise对象中的catch无视链接位置,都能捕获上层未捕捉过的错误,then3会执行因为catch会返回一个Promise,且由于这个Promise没有返回值,所以打印出来的是undefined。
Promise.resolve(1) .then(res => { console.log(res); return 2; }) .catch(err => { return 3; }) .then(res => { console.log(res); }); // 1 2
题目8️⃣:Promise对象的链式调用的执行顺序。Promise可以链式调用,不过promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用,return 2会被包装为resolve(2)。
Promise.reject('err!!!') .then((res) => { console.log('success', res) }, (err) => { console.log('error', err) }).catch(err => { console.log('catch', err) }) // error err!!! Promise.resolve() .then(() => { throw new Error('error!!!'); }) .then( function success(res) {}, function fail1(err) { console.log('fail1', err); } ) .catch(function fail2(err) { console.log('fail2', err); }); // fail1 Error: error!!!
题目9️⃣:注意then的第二参数错误处理与catch的区别。,两个都是处理reject状态回调结果或者是抛出的错误,如果存在第二参数,也存在catch,捕获错误是参数生效,否则就会catch生效。也可以这样理解then的第一个参数是处理成功的函数,第二个参数是处理失败的函数。如果两个都没有就会直接报错。
Promise.reject('err!!!') .then((res) => { console.log('success', res) }, (err) => { console.log('error', err) }).catch(err => { console.log('catch', err) }) // error err!!! Promise.resolve() .then(() => { throw new Error('error!!!'); }) .then( function success(res) {}, function fail1(err) { console.log('fail1', err); } ) .catch(function fail2(err) { console.log('fail2', err); }); // fail1 Error: error!!!
题目🔟:then参数是函数,对于非函数会出现值穿透。 如果then传入的是非函数,resolve会被传到是函数的地方。
Promise.resolve(1).then(2).then(Promise.resolve(3)) .then(console.log) // 1
题目1️⃣1️⃣:finally方法也是一个微任务。:.finally()方法不管Promise对象最后的状态如何都会执行。.finally()方法的回调函数不接受任何的参数。
function promise1() { let p = new Promise((resolve) => { console.log(1); resolve(2) }) return p; } function promise2() { return new Promise((resolve, reject) => { reject('error') }) } promise1() .then(res => console.log(res)) .catch(err => console.log(err)) .finally(() => console.log('finally1')) promise2() .then(res => console.log(res)) .catch(err => console.log(err)) .finally(() => console.log('finally2')) // 1 2 error finally1 finally2
题目1️⃣2️⃣:async await执行机制:在async1中await后面的Promise是没有返回值的,也就是它的状态始终是pending状态,因此相当于一直在await,await,await却始终没有响应,所以就不能执行await后面的语句了。
- async await宏任务:await强制的是当前async函数域,所以不能优先处理await后面的语句,全局同步任务可优先处理。
async function async1() { console.log(1); await async2(); console.log(2); } async function async2() { setTimeout(() => { console.log(3) }, 0) console.log(4); } async1(); console.log(5) // 1 4 5 2 3
- async await微任务:
async function async1() { console.log(1); await new Promise(resolve => { console.log(2) }) // Promise没有resolve所以一直处于pending console.log(3); return 'async1 end' } console.log(4) async1().then(res => console.log(res)) console.log(5) // 4 1 2 5
- async await微任务、宏任务:
async function testSometing() { console.log(1); return 2; } async function testAsync() { console.log(3); return Promise.resolve(4); } async function test() { console.log(5); const v1 = await testSometing(); console.log(v1); const v2 = await testAsync(); console.log(v2); console.log(v1, v2); } test(); var promise = new Promise(resolve => { console.log(6); resolve(7); }); promise.then(val => console.log(val)); console.log(8); // 5 1 6 8 2 3 7 4 // 2 4
题目1️⃣3️⃣:理解Promise.all方法,all方需要等所有异步操作执行完后才执行回调,由于有reject状态的回调,所以没有执行then,直接执行了catch。
function runAsync(x) { const p = new Promise(r => setTimeout(() => r(x, console.log('runAsync', x)), 1000)) return p } function runReject(x) { const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log('runRejct', x)), 1000 * x)) return p } Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)]) .then(res => console.log('then:', res)) .catch(err => console.log('catch:', err)) // runAsync 1 // runAsync 3 // runRejct 2 // catch: Error: 2 // runRejct 4
问题1️⃣4️⃣:理解Promise.race方法,race获取最快的哪一个异步操作的结果。由于下面执行了一个reject状态的回调,所以没有执行then,如果没有这个runReject(0),下面例子打印的是1 result:1 2 3。
function runAsync(x) { const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000) ); return p; } function runReject(x) { const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x) ); return p; } Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)]) .then(res => console.log("result: ", res)) .catch(err => console.log(err)); // 0 Error:0 1 2 3
问题1️⃣5️⃣: 构造函数里resolve之前的同步任务打印3 7优先执行,然后就是first后面的全局同步任务4,注意这里与上面的嵌套微任务的区别,因为这里直接resolve(2)的话first就会完成回调函数了,但是最外层构造函数内还有一个微任务p,所以先执行。
const first = () => (new Promise((resolve, reject) => { console.log(3); let p = new Promise((resolve, reject) => { console.log(7); setTimeout(() => { console.log(5); resolve(6); console.log(p) }, 0) resolve(1); }); resolve(2); p.then((arg) => { console.log(arg); }); })); first().then((arg) => { console.log(arg); }); console.log(4); // 3 7 4 1 2 5 fulfilled:1
问题1️⃣6️⃣:综合Promise值穿透、宏任务、微任务、async await
const async1 = async () => { console.log(1); setTimeout(() => { console.log(2) }, 2000) await new Promise(resolve => { console.log(3) }) console.log(4) return 5 } console.log(6); async1().then(res => console.log(res)); console.log(7); Promise.resolve(8) .then(9) .then(Promise.resolve(10)) .catch(11) .then(res => console.log(res)) setTimeout(() => { console.log(12) }, 1000) //6 1 3 7 8 12 2
问题1️⃣7️⃣: Promise的状态一旦改变就无法改变。.finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值。由于微任务Promise中有宏任务,并且有多个resolve,但是Promise值只能回调一个函数,所以打印了3,.finally也是一个微任务并且不接收回调函数参数,所以为undefined,然后执行微任务Promise中宏任务,接着执行then中的宏任务,此时p1状态是undefined(没有then处理)。
const p1 = new Promise((resolve) => { setTimeout(() => { resolve(1); console.log(2) }, 0) resolve(3); resolve(4); }).then(res => { console.log(res)// 打印3 setTimeout(() => { console.log(p1) }, 1000) }).finally(res => { console.log('finally:', res) }) // 3 finally:undefined 2 fulfilled:undefined
✍总结:其实学完这17道题目,希望各位同仁有收获,对于Promise的执行有更深理解与掌握。接下来就总结一下如何快速处理这种题目的技巧(从上到下按顺序理解):
- 🚩
先看是否同步任务
:对于非函数,先看当前语句是否是同步任务,是就先执行;对于函数,不管是同步还是异步操作,也是先执行同步任务。 - 🚩
异步任务判断是宏任务还是为微任务
:先执行微任务,后执行宏任务。 - 🚩
任务嵌套情况,灵活处理
:不管是微任务还是宏任务,先处理同级别任务,但是对于微任务有些情况特殊需要灵活理解。 - 🚩
Promise特点要重视
:
- 对象状态不受外界影响;状态一旦改变就不会再变。
- then传参必须是函数,否则出现值穿透。
- 理解catch处理与then第二个参数处理。
- Promise如果没有resolve或reject,就会一直处于pending状态。
- 区分all方法与race方法。
- 🚩
理解async await
:强制执行函数体内await当前语句完毕(也相当于一个微任务),才会执行函数体内await后面的语句。
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库