您现在的位置是:网站首页> 编程资料编程资料

万字详解JavaScript手写一个Promise_javascript技巧_

2023-05-24 373人已围观

简介 万字详解JavaScript手写一个Promise_javascript技巧_

前言

手写Promise现在已经成了面试的热门内容,但在实际开发中基本都不会去手写一个Promise,但是在面试中各种手写题可能就会遇到一个手写Promise,我们可以尽量提高我们的上限,从而获取更多的工作机会。

Promise核心原理实现

首先我们从使用的角度来分析一下Promise,然后编写一个最简单版本的Promise。

Promise的使用分析

Promise就是一个类,在执行这个类的时候,需要传递一个执行器(回调函数)进去,执行器会立即执行。

Promise中的状态分为三个,分别是:

  • pending→等待
  • fulfilled→成功
  • rejected→失败

状态的切换只有两种,分别是:

  • pending→fulfilled
  • pending→rejected

一旦状态发生改变,就不会再次改变:

  • 执行器中的两个参数,分别是resolve和reject,其实就是两个回调函数,调用resolve是从pending状态到fulfilled,调用reject是从状态pending到rejected。传递给这两个回调函数的参数会作为成功或失败的值。
  • Promise的实例对象具有一个then方法,该方法接受两个回调函数,分别来处理成功与失败的状态,then方法内部会进行判断,然后根据当前状态确定调用的回调函数。then方法应该是被定义在原型对象中的。
  • then的回调函数中都包含一个值,如果是成功,表示成功后返回的值;如果是失败就表示失败的原因。

MyPromise的实现

根据我们上面的分析,写出如下代码:

MyPromise.js // 定义所有状态的常量 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' // Promise实质上就是一个类,首先创建一个Promise的类 class MyPromise { // 实例化Promise时需要一个回调函数,该回调函数立即执行 constructor(executor) { // 在调用executor需要传递两个回调函数,分别是resolve和reject executor(this.resolve, this.reject) } // Promise 的状态 status = PENDING // 记录成功与失败的值 value = undefined reason = undefined resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象 // 形参value表示,调用resolve时传递的参数 // 如果当前状态不是pending,就直接跳出该逻辑 if (this.status !== PENDING) return // 将状态修改为成功 this.status = FULFILLED // 将传入的值进行保存 this.value = value } reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象 // 形参reason表示,调用reject时传递的失败的原因 // 如果当前状态不是pending,就直接跳出该逻辑 if (this.status !== PENDING) return // 将状态修改为失败 this.status = REJECTED // 保存失败的原因 this.reason = reason } // then方法的实现 then (onFulfilled, onRejected) { // 判断当前状态,根据状态调用指定回调 if (this.status === FULFILLED) { // 将成功的值作为参数返回 onFulfilled(this.value) } else if (this.status === REJECTED) { // 将失败的原因作为参数返回 onRejected(this.reason) } } } // 导出Promise module.exports = MyPromise 

现在我们就来写一段代码验证一下上面的代码

验证resolve:

const MyPromise = require('./myPromise') let promise = new MyPromise((resolve, reject) => { resolve('成功') }) promise.then(value => { console.log(value); }, reason => { console.log(reason); }) /* 输出 成功 */

验证reject:

const MyPromise = require('./myPromise') let promise = new MyPromise((resolve, reject) => { reject('失败') }) promise.then(value => { console.log(value); }, reason => { console.log(reason); }) /* 输出 失败 */

验证状态不可变:

const MyPromise = require('./myPromise') let promise = new MyPromise((resolve, reject) => { resolve('成功') reject('失败') }) promise.then(value => { console.log(value); }, reason => { console.log(reason); }) /* 输出 成功 */ 

在Promise中加入异步操作

如果我们的代码中存在异步操作,我们自己写的Promise将毫无用处,

例如下面这段代码:

const MyPromise = require('./myPromise') let promise = new MyPromise((resolve, reject) => { setTimeout(resolve, 2000, '成功') }) promise.then(value => { console.log(value); }, reason => { console.log(reason); })

这段代码2000ms后没有任何输出,为了解决这个问题我们将对自己编写的类进行如下操作:

  • 创建两个实例方法用于存储我们传入的成功与失败的处理逻辑。
  • then方法添加状态为pending时的处理逻辑,这时将传递进来的属性保存到实例上。
  • 在成功或者失败时调用相应的传递进来的回调函数(实例属性存在函数的情况下)。

实现代码如下:

// MyPromise.js const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { constructor(executor) { executor(this.resolve, this.reject) } status = PENDING value = undefined reason = undefined // 存储成功与失败的处理逻辑 onFulfilled = undefined onRejected = undefined resolve = (value) => { if (this.status !== PENDING) return this.status = FULFILLED this.value = value // 如果将状态修复为成功,调用成功的回调 this.onFulfilled && this.onFulfilled(this.value) } reject = (reason) => { if (this.status !== PENDING) return this.status = REJECTED this.reason = reason // 如果将状态修复为失败,调用失败的回调 this.onRejected && this.onRejected(this.reason) } then (onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } else if (this.status === REJECTED) { onRejected(this.reason) } else { // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调 this.onFulfilled = onFulfilled this.onRejected = onRejected } } } module.exports = MyPromise

这里的this.onFulfilled && this.onFulfilled(this.value)表示如果该属性存在就调用这个方法。

现在我们重新执行一开始上面那一段代码,2s后会成功输出成功

实现then方法的多次调用

Promise实例中存在要给then方法,允许我们在Promise实例中链式调用,每个then方法还会返回一个Promise实例,

如下图所示:

Promise实例方法then是可以多次进行调用,而我们现在自己封装的却执行调用一次,现在根据新需要来重新改写我们的代码,

实现思路如下:

  • 定义可以存储多个回调的数组,用于存储多个回调函数。
  • 如果是同步执行的代码,执行后立即知道执行结果,所以可以直接调用回调函数。
  • 如果是异步代码,需要将每次回调函数保存到数组中,然后状态变化时依次调用函数。

实现代码如下:

// MyPromise.js const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { constructor(executor) { executor(this.resolve, this.reject) } status = PENDING value = undefined reason = undefined // 存储成功与失败的处理逻辑 onFulfilled = [] onRejected = [] resolve = (value) => { if (this.status !== PENDING) return this.status = FULFILLED this.value = value // 如果将状态修复为成功,调用成功的回调 while (this.onFulfilled.length) { // Array.prototype.shift() 用于删除数组第一个元素,并返回 this.onFulfilled.shift()(this.value) } } reject = (reason) => { if (this.status !== PENDING) return this.status = REJECTED this.reason = reason // 如果将状态修复为失败,调用失败的回调 while (this.onRejected.length) { // Array.prototype.shift() 用于删除数组第一个元素,并返回 this.onRejected.shift()(this.reason) } } then (onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } else if (this.status === REJECTED) { onRejected(this.reason) } else { // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调 this.onFulfilled.push(onFulfilled) this.onRejected.push(onRejected) } } } module.exports = MyPromise

这里我们通过数组的shift()方法,该方法删除数组的第一个元素,并返回,返回的这个值正好是一个回调函数,然后调用该函数即可实现该功能。

实现then的链式调用

想要实现then的链式调用,主要解决两个问题:

  • 返回的是一个新的MyPormise实例。
  • then的返回值作为下一次的链式调用的参数。

这里分为两种情况:

  • 直接返回一个值,可以直接作为值使用
  • 返回一个新的MyPormise实例,此时就需要判断其状态

实现代码如下:

// MyPromise.js /* 省略的代码同上 */ class MyPromise { /* 省略的代码同上 */ // then方法的实现 then (onFulfilled, onRejected) { // then 方法返回一个MyPromise实例 return new MyPromise((resolve, reject) => { // 判断当前状态,根据状态调用指定回调 if (this.status === FULFILLED) { // 将成功的值作为参数返回 // 保存执行回调函数的结果 const result = onFulfilled(this.value) // 如果返回的是一个普通的值,直接调用resolve // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject resolvePromise(result, resolve, reject) } else if (this.status === REJECTED) { // 将失败的原因作为参数返回 onRejected(this.reason) } else { // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调 this.onFulfilled.push(onFulfilled) this.onRejected.push(onRejected) } }) } } function resolvePromise (result, resolve, reject) { // 判断传递的result是不是MyPromise的实例对象 if (result instanceof MyPromise) { // 说明是MyPromise的实例对象 // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调 // result.then(value => resolve(value), reason => reject(reason)) // 简写 result.then(resolve, reject) } else { resolve(result) } } module.exports = MyPromise

then方法链式调用识别Promise对象自返回

在Promise中,如果then方法返回的是自己的promise对象,则会发生promise的嵌套,这个时候程序会报错,

测试代码如下:

var promise = new Promise((resolve, reject) => { resolve(100) }) var p1 = promise.then(value => { console.log(value) return p1 }) // 100 // Uncaught (in promise) TypeError: Chaining cycle detected for promise #

想要解决这个问题其实我们只需要在then方法返回的MyPromise实例对象与then中回调函数返回的值进行比对,如果相同的返回一个reject的MyPromis

-六神源码网