[那些被问烂的前端面试题]-手写Promise有什么含金量与考点?

大家好,这里是梦兽编程。虽然梦兽编程是非常讨厌八股文面试。因为有些时候,我想说我压根不知道的面试官想问的考点是什么,因为大部分的面试官也是背题的。

技术探讨本来是经验的分享,而在我国的情况变成了9年义务教育的死板"教科书"。甚至有很多背了这些知识完后都不知道是用来做什么的.等下次要用的时候继续背题。

其实有些面试题含金量量也是很高的,梦兽编程将会把感觉的含金量比较高的知识点放到这个栏目中进行更新迭代更新那些被问烂的前端面试题

手写Promise的考点与知识点

手写Promist这题是一道很好的面试题,如果是大厂一般都会考察吧。这个题目考察的内容比较广泛。

  • 考察你是否熟悉Promise

我感觉很多使用Promise的使用者估计有65%不知道Promise拥有3个状态,再使用Promise.all的时候发现问题才有人知道这个经验吧

"

  • 考察你的编码能力与项目经验,因为设计一个Promise需要用到一些代码封装才能实现。

    • 代码封装能力
    • 是否掌握事件循环
    • 对观察者模式(响应式)的理解
    • 是否理解递归

为什么需要Promise

很多都回说解决回调地狱,但是也有其他的人回理解Promise.then不也是一种回调方式吗?为什么就是Promise是解决回调函数呢?

核心是解决嵌套问题,回调函数是一种很说错的设计模式来的。

我们应该什么时候需要进行封装呢?,如果你有往Linux进行PR操作都知道Linux有一条不需出现三个嵌套的规则。

scss 复制代码
run(0)
setTimeout(()=>{
 run(1);
 setTimeout(()=>{
  run(2);
 })
})

随着业务的不断叠加,我们的业务也会不断叠加,那我们的嵌套关系也就越来越深。如果你使用Promise,那么Promise的就可以将嵌套关系变成平铺关系,入下面代码所示:

scss 复制代码
Promise
.then()
.then()
.then()

如何使用Promise

在手写Promise之前,你得懂使用Promise才能知道如何设计这个代码。我们先回顾一下Promise的使用场景

javascript 复制代码
const p1 = new Promise((resolve, reject) => {
  console.log('create a promise');
  resolve('成功了');
})

console.log("after new promise");

const p2 = p1.then(data => {
  console.log(data)
  throw new Error('失败了')
})

const p3 = p2.then(data => {
  console.log('success', data)
}, err => {
  console.log('faild', err)
})
  • 我们会先调用一个Promise对象,这个Promise对象参数是一个执行函数(resolve, reject) => void,如果成功调用resolve否则是reject。
  • Promise拥有三个状态,如果你不知道这三个状态是无法复现一个Promise的。这三个状态分别为PENDING,FULFILLED,REJECTED。分别是等待态,成功态,失败态

而且这些状态是不可逆的。

Promise 一步一步实现

按照这个思路你一定能全都记住!

按照Promise的结构进行设计

在上面我们知道Promise拥有的一些属下可以这么设计我们的MyPromise对象。

javascript 复制代码
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class MyPromise {
    // 我们需要new 的时候传参一个执行函数,所以在考察一些事件驱动的面试题的时候我们就明白为什么
    // Promise new 的时候会马上执行,因为这里是宏任务。
    constructor(executor) {
        // Promise的状态为PENDING,如果不转变状态那么then是不会执行的,在all的
        // 有不执行的情况可以查阅每个Promise的状态
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;

        let resolve = (value) => {

        }

        let reject = (reason) => {

        }
    }

    then(onFulfilled, onRejected) {
    }
}

const promise = new MyPromise((resolve, reject) => {
    console.log('执行')
    resolve(1);
})

promise.then((res) => {
    console.log('success', res)
})

没有执行任何结果?

运行以上代码我们会发现无法执行的,还记得我们的那个参数的执行函数吗?是的我们并没有进行执行。我们只需要在构造函数里执行一下就可以了。

ini 复制代码
constructor(executor) {
        // coding...
		// 不要忘记修改状态
		 let resolve = (value) => {
			 if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
            }
        }

        let reject = (reason) => {
 			if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
            }
        }

		try {
		  executor(resolve,reject)
		} catch (error) {
		  reject(error)
		}
}

我们再次执行

javascript 复制代码
node .\Promise.mjs
执行

为什么then没有执行?

按照Promise的设计,这个时候我们需要让then后面的代码执行起来。但是我们并没有去实现这部分的代码:

kotlin 复制代码
// 这个过程我们需要理清楚状态的概念
then(onFulfilled, onRejected) {
	if (this.status === FULFILLED) {
	  // 让回调函数执行
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
}

再执行代码我们可以看到:

javascript 复制代码
node .\Promise.mjs
执行
success 1

如何让它执行异步-观察者模式

上面的代码看着好像没什么问题?但是如果我们延迟执行以下onFulfilled的代码会发现一个严重的问题then的逻辑代码没有执行

javascript 复制代码
const promise = new MyPromise((resolve, reject) => {
    console.log('执行')
    setTimeout(() => {
        resolve(1);
    }, 0);
})

promise.then((res) => {
    console.log('success', res)
})

这个时候我们就需要使用观察者驱动它可以执行异步的方式,如果状态改变再执行回每个then的所有方法。

kotlin 复制代码
constructor(executor) {
	// code...
	// 存放成功的回调
    this.onResolvedCallbacks = [];
    // 存放失败的回调
    this.onRejectedCallbacks= [];
	
	 let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次将对应的函数执行
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 
	
	let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 依次将对应的函数执行
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }
	
}

then(onFulfilled, onRejected) {
 // code...
 if (this.status === PENDING) {
      // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      });

      // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行
      this.onRejectedCallbacks.push(()=> {
        onRejected(this.reason);
      })
    }
}

then的链式调用(这里的难点就比较高了)

这里需要掌握的事递归,往往递归写的好不好真的很程序员的水平。这里梦兽编程就多讲了,水平有限不敢多说,

补充完整代码

ini 复制代码
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

const resolvePromise = (promise2, x, resolve, reject) => {
  // 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise  Promise/A+ 2.3.1
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // Promise/A+ 2.3.3.3.3 只能调用一次
  let called;
  // 后续的条件要严格判断 保证代码能和别的库一起使用
  if ((typeof x === 'object' && x != null) || typeof x === 'function') { 
    try {
      // 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)  Promise/A+ 2.3.3.1
      let then = x.then;
      if (typeof then === 'function') { 
        // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
        then.call(x, y => { // 根据 promise 的状态决定是成功还是失败
          if (called) return;
          called = true;
          // 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
          resolvePromise(promise2, y, resolve, reject); 
        }, r => {
          // 只要失败就失败 Promise/A+ 2.3.3.3.2
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        // 如果 x.then 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      // Promise/A+ 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 如果 x 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.4  
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

    try {
      executor(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    //解决 onFufilled,onRejected 没有传值的问题
    //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    // 每次调用 then 都返回一个新的 promise  Promise/A+ 2.2.7
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        //Promise/A+ 2.2.2
        //Promise/A+ 2.2.4 --- setTimeout
        setTimeout(() => {
          try {
            //Promise/A+ 2.2.7.1
            let x = onFulfilled(this.value);
            // x可能是一个proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            //Promise/A+ 2.2.7.2
            reject(e)
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        //Promise/A+ 2.2.3
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e)
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(()=> {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0);
        });
      }
    });
  
    return promise2;
  }
}

本文使用 markdown.com.cn 排版

相关推荐
天下无贼!1 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr1 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林1 小时前
npm发布插件超级简单版
前端·npm·node.js
我码玄黄2 小时前
THREE.js:网页上的3D世界构建者
开发语言·javascript·3d
罔闻_spider2 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔2 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
小晗同学2 小时前
Vue 实现高级穿梭框 Transfer 封装
javascript·vue.js·elementui
盏灯2 小时前
前端开发,场景题:讲一下如何实现 ✍电子签名、🎨你画我猜?
前端
宇宙李2 小时前
2024java面试-软实力篇
面试·职场和发展