【面试题】Promise只会概念远远不够,还需这17道题目巩固!

简介: 【面试题】Promise只会概念远远不够,还需这17道题目巩固!

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

表妹一键制作自己的五星红旗国庆头像,超好看

在学习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后面的语句。

前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

 表妹一键制作自己的五星红旗国庆头像,超好看

相关文章
|
6月前
|
存储 网络协议 安全
嵌入式面试题目汇总之经典
嵌入式面试题目汇总之经典
75 1
|
19天前
|
Java 关系型数据库 MySQL
大厂面试题详解:Java抽象类与接口的概念及区别
字节跳动大厂面试题详解:Java抽象类与接口的概念及区别
40 0
|
1月前
|
JavaScript 前端开发 API
vue面试题目汇总
vue面试题目汇总
37 4
|
1月前
|
SQL 存储 关系型数据库
C# .NET面试系列十:数据库概念知识
#### 1. 为什么要一定要设置主键? 设置主键是数据库设计中的一个重要概念,有几个主要原因: 1、唯一性 ```c# 主键必须保证表中的每一行都有唯一的标识。这样可以避免数据冗余和不一致性。如果没有主键或者主键不唯一,就可能出现数据混乱或错误。 ``` 2、查询性能 ```c# 数据库系统通常会使用主键来加速数据检索。主键通常会被索引,这样可以更快速地找到特定行的数据,提高查询效率。 ``` 3、关联性 ```c# 主键常常用于建立表与表之间的关系。在关系数据库中,一个表的主键通常与其他表中的外键建立关联,这种关系对于数据的一致性和完整性非常重要。 ``` 4、数据完
131 1
C# .NET面试系列十:数据库概念知识
|
1月前
|
算法 Linux 调度
嵌入式linux面试题目总结
嵌入式linux面试题目总结
38 0
|
2月前
|
安全 Java 编译器
Go语言面试宝典:50道必会题目与精解
本文提供了50道覆盖Go语言核心概念、并发编程、内存管理、包管理、错误处理和测试等方面的面试题及其详细答案,旨在帮助开发者全面准备Go语言技术面试。
|
2月前
|
存储 机器学习/深度学习 算法
【数据结构入门精讲 | 第二篇】考研408、企业面试基础概念习题
【数据结构入门精讲 | 第二篇】考研408、企业面试基础概念习题
51 0
|
2月前
|
Linux
面试题12: 基本Linux 命令题目
面试题12: 基本Linux 命令题目
|
3月前
|
存储 JavaScript 安全
Vue基础面试题题目一
Vue基础面试题题目一
28 0
|
3月前
|
存储 算法 Java
盛算信息-面试经历-笔试部分-完整题目(一)
盛算信息-面试经历-笔试部分-完整题目(一)
32 2