前端面试之问到promise怎么办?

Tiger老师

前言

Promise作为面试中的经典考题,我们一定要深刻学习和理解它! Promise有什么用呢?答:我们拿它解决异步回调问题。Pomise是ES6里面新增的一种异步编程的解决方案。现在这个promise在面试中感觉就像“css清除浮动”一样,属于答不上来就会挂掉的前端基础知识了。

本文大纲:

1.promise基本用法;

2.promise A+手动实现

3.promise 使用中的哪些坑 promise.all 回调地狱等;

(一)promise基本用法

promise对象简单的来说,有点类似于ajax,它可以看做是一个装有某个未来才会结束的事件的容器。它有两个特点:1,promise的状态不受外部影响;2.promise的状态一旦改变,就不会再变了。

可以先看下promise的结构

function Promise(executor){

var self = this

self.status = 'pending' // Promise当前的状态

self.data = undefined // Promise的值

self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

executor(resolve, reject) // 执行executor并传入相应的参数

}

promise的结构简单来说就是,它有个status表示三种状态,pending(挂起),resolve(完成),reject(拒绝)。除此之外,它还有两个回调方法onResolvedCallbackonRejectedCallback,分别对应resolve(完成) 和reject(拒绝)。

假如前面面试官问你promise的概念和基本用法,你像我一样提到了ajax的话,面试官很可能就会顺口问一下ajax(毕竟也是前端应该掌握得基础知识之一)。根据我的经验来看,一线大厂的面试官这个时候很可能会要你用promise撸一个ajax出来,一来可以考察你promise和ajax掌握得怎么样,二来可以考察你的代码能力。

1.1用promise来实现Ajax

const $ = (function(){

const ajax = function(url, async = false, type = 'GET'){

const promise = new Promise(function(resolve, reject){

const handler = function(){

if(this.readyState !== 4){

return;

}

if(this.status === 200){

resolve(this.response);

}else{

reject(new Error(this.statusText));

}

}

const client = new XMLHttpRequest();

client.open(type, url);

client.onreadystatechange = handler;

client.responseType = 'json';

client.setRequestHeader("Accept", "application/json");

client.send();

})

return promise;

}

return {

ajax

};

})()

调用方式:

$.ajax("/posts.json").then(function(json) {

console.log('Contents: ' + json);

}, function(error) {

console.error('出错了', error);

});

(二)手动实现一个 Promise/A

要注意的几个点:1.then方法会返回一个新的promise,因此then方法应该写到原型链上。2.promise 的返回值或者抛出的err 会有传递现象。

例如:

new Promise(resolve=>resolve(8))
.then()
.catch()
.then(function(value) {
alert(value)
})

// 根据promise的定义和调用方式,可以先写出promise的数据结构

function Promise(executor){

const _this = this;

_this.status = 'pending';

_ths.data = undefined;

_this.onRejectedCallback = [];

_this.onResolvedCallback = [];

function resolve(value){

if(_this.status === 'pending'){

_this.status = 'resolved';

_this.data = value;

for(let i=0;i<_this.onResolvedCallback.length;i++){

_this.onResolvedCallback[i](value);

}

}

}

function reject(reason){

if(_this.status === 'pending'){

_this.status = 'rejected';

_this.data = reason;

for(let i=0;i<_this.onResolvedCallback.length;i++){

_this.onRejectedCallback[i](reason);

}

}

}

try{

executor(resolve, reject);

}catch (e){

reject(e)

}

}

// then方法应该写在原型链上

Promise.prototype.then = function(onResolved, onRejected){

const self = this;

// 要判断onResolved 和 onRejected是不是方法

onResolved = typeof onResolved === 'function' ? onResolved : function(value) { return value }

onRejected = typeof onRejected === 'function' ? onRejected : function(reason) { return reason }

if(self.status === 'resolved'){

return new Promise(function(resolve, reject){

try{

const resoult = onResolved([self.data](https://link.zhihu.com/?target=http%3A//self.data));

if( resoult instanceof Promise ){ // 如果返回的是新的promise,那么用这个promise的痛恨方法

resoult.then(resolve, reject)

}

resolve(resoult) // 否则 直接讲返回值作为newPromise的结果

}.catch(e){

reject(e);

}

});

}

// 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释

if (self.status === 'rejected') {

return new Promise(function(resolve, reject) {

try {

var resoult = onRejected([self.data](https://link.zhihu.com/?target=http%3A//self.data))

if (resoult instanceof Promise) {

resoult.then(resolve, reject)

}

} catch (e) {

reject(e)

}

})

}

if(self.status === 'pending'){

return new Promise(function(){});

}

if (self.status === 'pending') {

// 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,

// 只能等到Promise的状态确定后,才能确实如何处理。

// 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里

// 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释

return Promise(function(resolve, reject) {

self.onResolvedCallback.push(function(value) {

try {

var x = onResolved([self.data](https://link.zhihu.com/?target=http%3A//self.data))

if (x instanceof Promise) {

x.then(resolve, reject)

}

} catch (e) {

reject(e)

}

})

self.onRejectedCallback.push(function(reason) {

try {

var x = onRejected([self.data](https://link.zhihu.com/?target=http%3A//self.data))

if (x instanceof Promise) {

x.then(resolve, reject)

}

} catch (e) {

reject(e)

}

})

})

}

}

(三)promise 的使用中应该要避免哪些坑

在面试的时候,如果能答出来promise的使用中可能会出现什么坑,已经如何避免这些坑。相信能够给面试官一个好印象,尤其是面试2年工作经验的岗位的时候,通过这些就能很好的和培训班毕业的假简历区分开来。而且这些注意的点也是我本人在项目中实实在在踩过的坑。

注意点1,不要刻意为了美化代码而避免使用嵌套结构。

很多promise的科普教程里,作者都会强调,为了代码的间接性,尽量不要使用嵌套结构。ES7里还为了处理这个事情,专门设计了async await语法。但很多新手,再没有充分理解业务的前提下,盲目的为了美化代码,为了避免“回调地狱”,经常会造成很大的问题。

1.1promis.all 的坑

设想一个场景,比如我们写的一个表单组件。如下图所示:

这里有三个选项组件,每个组件都对应一个字典。当时组里的一个实习生,为了简洁美化代码,在这样一个组件里使用的Promise.all()。

类似于这样:

fetchData1 = function (){ // 请求组件1的字典};

fetchData2 = function (){ // 请求组件2的字典};

fetchData3 = function (){ // 请求组件3的字典};

Promise.all([fetchData1, fetchData2, fetchData3 ]);

当时看这段代码,并没有发现什么问题。结果后来一生产环境,问题就出来,控件1硬是获取不到字典项,但是获取组件1字典的接口,怎么查都是好的。最后只能重新看一遍组件的源代码,才发现了问题。原理是控件2的接口出现了问题,导致于整个Promise.all请求报错。

所以,在使用promise.all的时候要注意:业务上没有必然关联的请求比如联动组件这种,一定不要使用promise.all。

2.回调地狱并不可怕,不要盲目的使用async await

下面是比较常见的前端代码:

asycn ()=>{

await 获取订单1的数据;

await 获取订单2的数据;

......

}

当订单2的数据与订单1的数据直接没有相互依赖的关系的时候。获取订单2的执行时间就多了一倍的订单1的时间。同样的道理,假如后面还有订单3,订单4,那浪费的时间就更多了。这也是会造成前端页面卡顿的主要原因。

面对这样的常考题型,我觉得也要认真对待,因为面试中如果仅仅只是背答案,也很可能会挂掉。假如面试官看出来你在背答案,他只需要把相关的知识点都问一下,或者让你手动实现一下,又或者问你在项目中遇到了什么坑,你是怎么处理的。准备不充分的面试者,一下子就会露出马脚。

所以小编为大家准备了前端面试题资料,之前小编把前端面试问题的知识点整理成PDF文档,方便自己查阅学习,现在免费分享给大家,小编新建了一个前端学习圈,欢迎大家加入学习聊天哦~希望大家在里面有所收获也聊得开心!小伙伴们点击这里进群玩并取资料哦!
image.png

image.png

最近和小伙伴聊天了解到面试问题问vue比较多些,小编把vue相关的面试资料一起分享给大家,也是一样点击这里免费获取哦!
image.png

image.png

篇幅有限,小编没有展示完,需要文章中出现的全部资料的,点击获取来源:前端面试题资料就好喽,祝各位能在自己的人生找到属于自己的职场生涯!

阅读 145
22 声望
3 粉丝
0 条评论
你知道吗?

22 声望
3 粉丝
宣传栏