前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
表妹一键制作自己的五星红旗国庆头像,超好看
在学习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) => {
resolve(1);
reject("error");
resolve(2);
});
promise.then(res => {
console.log("then: ", res);
}).catch(err => {
console.log("catch: ", err);
})
// then:1
题目7️⃣:Promise对象中的catch无视链接位置,都能捕获上层未捕捉过的错误,then3会执行因为catch会返回一个Promise,且由于这个Promise没有返回值,所以打印出来的是undefined。
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
题目8️⃣:Promise对象的链式调用的执行顺序。Promise可以链式调用,不过promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用,return 2会被包装为resolve(2)。
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
// 1 2
题目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后面的语句。
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
表妹一键制作自己的五星红旗国庆头像,超好看