漂亮的代码会说话——手写Promise

1,082 阅读6分钟

优雅,永不过时

尝试实现Promise类,并且追求代码的美感。

// promise对象的构造与使用
const promise = new Promise((resolve, reject) => {
    resolve('promise成功了');
})

promise.then((value) => {
    console.log(value)
})

看到promise对象的构造与使用,试想我们要写这样的一个类,大概要写些什么?粗劣一想,这两点必须有:

  • 有一个构造函数,一个executor functon作为参数
  • 有一个then方法,可以传递一到两个回调作为参数

这就来大概写一下:

class Promise {
    constructor(executor){
        this.state = 'pending'; // pending, fulfilled, rejected 三种状态
    
        // 立即执行
        executor(this._resolve, this._reject);
    }
  
    // #region 私有方法
    _resolve = (value) => {
    }
    _reject = (reason) => {
    }
    // #endregion 私有方法
    
    // #region 公有方法
    then(successCallback, errorCallback) {
    }
    // #endregion 公有方法
}

优雅一下

  • 私有方法已_开头,便于区分公私方法。
  • 公私方法分堆摆放,用#region/#endregion这样的注释,这种注释vscode是支持将#region/#endregion中间的代码整体折叠的呦。

状态相关实现

一个 Promise 必然处于以下几种状态之一:待定(pending)、已兑现(fulfilled)、已拒绝(rejected)。异步操作最终得到成功返回值或者失败原因。我们实现一下这部分逻辑。

  • 需要三个字段state(表示状态),value(成功的值),reasion(失败的原因)
  • 需要两个字段successCallbacks(存储成功回调)、errorCallbacks(存储失败回调)
  • _resolve方法做两件事,改变state状态为fulfilled和执行所有成功回调
  • _reject方法做两件事,改变state状态为rejected和执行所有失败回调
class Promise {
    constructor(executor){
        this.state = 'pending'; // pending, fulfilled, rejected 三种状态
        this.value = undefined;
        this.reasion = undefined;
        
        this.successCallbacks = []
        this.errorCallbacks = []
    
        // 立即执行
        executor(this._resolve, this._reject);
    }
  
    // #region 私有方法
    _resolve = (value) => {
        if (this.state === 'pending') {
            this.state = 'fulfilled';
            this.value = value;
            
            this.successCallbacks.forEach(callback => callback());
        }
    }
    _reject = (reason) => {
        if (this.state === 'pending') {
            this.state = 'rejected';
            this.reason = reason;
            
            this.errorCallbacks.forEach(callback => callback());
        }
    }
    // #endregion 私有方法
    
    // #region 公有方法
    then(successCallback, errorCallback) {
        this.successCallbacks.push(successCallback)
        this.errorCallbacks.push(errorCallback)
    }
    // #endregion 公有方法
}

优雅一下

我们对现有代码进行重构,让其更优雅一些:

class Promise {
   // 构造器干两件事,初始化字段和执行executor。
   constructor(executor){
        // 1. 初始化字段
        this._initProps();
        // 2,执行executor
        executor(this._resolve, this._reject);
   }
  
    // #region 私有方法
    _initProps = () => {
        this.state = 'pending'; // pending, fulfilled, rejected 三种状态
        this.value = undefined;
        this.reasion = undefined;
         
        this.successCallbacks = []
        this.errorCallbacks = []
    }
  
   _resolve = (value) => {
     if(this.state !== 'pending') return; // 使用卫句,减少if嵌套
     
     this.state = 'fulfilled';
     this.value = value;

     this.successCallbacks.forEach(callback => callback(value));
    }
    _reject = (reason) => {
      if(this.state !== 'pending') return;
      
      this.state = 'rejected';
      this.reason = reason;

      this.errorCallbacks.forEach(callback => callback(reason));
    }
    // #endregion 私有方法
  
   // #region 公有方法
   then(successCallback: value => value, errorCallback) {
     this.successCallbacks.push(successCallback)
     this.errorCallbacks.push(errorCallback)
   }
   // #endregion 公有方法
   
}

优雅一下

  • 清晰的构造器逻辑,更容易让人理解你代码的逻辑。
  • 使用卫句,可以减少嵌套,也更容易让人注意到方法的主体逻辑。
  • 注意空行的使用,让逻辑关系紧密的代码在一块,增加代码可读性。

then方法实现

将之前写过都代码折叠,只看then方法。

 class Promise {
    // 构造器
    ...
    
    // #region 私有方法
    ...
    _thenWhenStateIsPending = (successCallback, errorCallback) => {
        this.successCallbacks.push(() => { successCallback })
        this.errorCallbacks.push(errorCallback)
    }
    _thenWhenStateIsFulfilled = (successCallback) => {
      setTimeout(() => {
        successCallback(this.value)
      }, 0);
    }
    _thenWhenStateIsRejected = (errorCallback) => {
       setTimeout(() => {
        errorCallback(this.reason)
      }, 0);
    }
  // #endregion 私有方法
  
  // #region 公有方法
   then(successCallback = (value => value), errorCallback = (err => { throw err })) {
     switch(this.state) {
       case 'pending':
         this._thenWhenStateIsPending(successCallback, errorCallback);
         break;
       case 'fulfilled':
         this._thenWhenStateIsFulfilled(successCallback);
         break;
       case 'rejected':
         this._thenWhenStateIsRejected(errorCallback);
         break;
     }
   }
  // #endregion 公有方法
}

优雅一下

  • 注意方法的单一职责,then方法是单一职责的,清晰都表达出会根据promise的不同状态做不同的处理。_thenWhenStateIsPending,_thenWhenStateIsFulfilled,_thenWhenStateIsRejected更是单一职责的,只处理一种状态下的then。
  • 讲究都命名,处理then相关的私有方法,都叫then***,这增加了它们的相关性。
  • 使用函数参数默认值,减少代码行数。

链式调用实现

then方法一定会返回一个新的promise对象

then方法一定会返回一个新的promise对象,所以我们这样写,声明一个promise变量,要求每个子then***方法到返回值都是promise对象,方法的最后把promise对象返回回去:


then(successCallback = (value => value), errorCallback = (err => { throw err })) {
 let promise = null;
 
 switch(this.state) {
   case 'pending':
     promise = this._thenWhenStateIsPending(successCallback, errorCallback);
     break;
   case 'fulfilled':
     promise = this._thenWhenStateIsFulfilled(successCallback);
     break;
   case 'rejected':
     promise = this._thenWhenStateIsRejected(errorCallback);
     break;
 }
 
 return promise;
}

对于各个子then方法,这样写,每个then***方法都构造一个新的promise对象返回出去,另外定义一个_resolvePromise方法用来处理这个新创建的promise对象的状态:

_resolvePromise = (promise, resolve, reject, result) => {
    // 处理then方法新创建的promise。
    // 如果result是promise对象,则处理result对象
}

_thenWhenStateIsPending = (successCallback, errorCallback) => {
    const promise = new Promise((resolve, reject) => {
      this.successCallbacks.push(() => {
        const result = successCallback(this.value)
        this._resolvePromise(promise, resolve, reject, result)
      })
      this.errorCallbacks.push(() => {
        const result = errorCallback(this.reason)
        this._resolvePromise(promise, resolve, reject, result)
      })
    }) 
    return promise
}
_thenWhenStateIsFulfilled = (successCallback) => {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const result = successCallback(this.value)
        this._resolvePromise(promise, resolve, reject, result)
      }, 0)
    })
    return promise
}
_thenWhenStateIsRejected = (errorCallback) => {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const result = errorCallback(this.reason)
        this._resolvePromise(promise, resolve, reject, result)
      }, 0)
    })
    return promise
}

实现_resolvePromise方法

_resolvePromise = (promise, resolve, reject, result) => {
    if (result === promise) return reject(new Error('promise链死循环'))
    if (result == null || result == undefined) return resolve(result)
    if (typeof result !== 'object' && typeof result !== 'function') return resolve(result)
    if (typeof result.then !== 'function') return resolve(result)
    
    // 经历了一连串的卫句,剩下的result应该是个promise对象,或者说有then方法的对象
    result.then(
      (newResult) => {
        this._resolvePromise(promise, resolve, reject, newResult)
      },
      (err) => {
        reject(err)
      }
    )
}

新增公有方法catch、resolve、reject、race、all

// #region 公有方法
catch(errorCallback) {
    this.then(null, errorCallback);
}
  
resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
}

reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
}

race(promises) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(resolve, reject)
      }
    })
}

all(promises) {
    // 返回一个promise实例
    return new Promise((resolve, reject) => {
      // 做一个判断参数是否是数组
      if (!Array.isArray(promises)) {
        return reject(new TypeError('arguments must be Array'))
      }
    
      let count = 0
      let newValues = new Array(promises.length) // 接收新的结果参数 建立一个伪数组
      for (let i = 0; i < promises.length; i++) {
        // 运用promise特性 只会有一个状态
        Promise.resolve(promises[i]).then(
          (res) => {
            count++
            newValues[i] = res // 把每次返回成功的数据添加到数组中
            if (count === promises.length) {
              // 数据接收完成
              return resolve(newValues)
            }
          },
          (rej) => reject(rej)
        )
      }
    })
}

// #endregion 公有方法

Promise类全部代码

class Promise {
  constructor(executor) {
    this._initProps()
    // 立即执行
    try {
      executor(this._resolve, this._reject)
    } catch (e) {
      this._reject(e)
    }
  }

  // #region 私有方法
  _initProps = () => {
    this.state = 'pending' // pending, fulfilled, rejected 三种状态
    this.value = undefined
    this.reasion = undefined

    this.successCallbacks = []
    this.errorCallbacks = []
  }

  _resolve = (value) => {
    if (this.state !== 'pending') return

    this.state = 'fulfilled'
    this.value = value

    this.successCallbacks.forEach((callback) => callback())
  }
  _reject = (reason) => {
    if (this.state !== 'pending') return

    this.state = 'rejected'
    this.reason = reason

    this.errorCallbacks.forEach((callback) => callback())
  }

  _resolvePromise = (promise, resolve, reject, result) => {
    if (result === promise) return reject(new Error('promise链死循环'))
    if (result == null || result == undefined) return resolve(result)
    if (typeof result !== 'object' && typeof result !== 'function') return resolve(result)
    if (typeof result.then !== 'function') return resolve(result)

    // 经历了一连串的卫句,剩下的result应该是个promise对象,或者说有then方法的对象
    try{
      result.then(
        (newResult) => {
          this._resolvePromise(promise, resolve, reject, newResult)
        },
        (err) => {
          reject(err)
        }
      )
    } catch (e){
      reject(e)
    }
  }

  _thenWhenStateIsPending = (successCallback, errorCallback) => {
    const promise = new Promise((resolve, reject) => {
      this.successCallbacks.push(() => {
        const result = successCallback(this.value)
        this._resolvePromise(promise, resolve, reject, result)
      })
      this.errorCallbacks.push(() => {
        const result = errorCallback(this.reason)
        this._resolvePromise(promise, resolve, reject, result)
      })
    })
    return promise
  }
  _thenWhenStateIsFulfilled = (successCallback) => {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const result = successCallback(this.value)
        this._resolvePromise(promise, resolve, reject, result)
      }, 0)
    })
    return promise
  }
  _thenWhenStateIsRejected = (errorCallback) => {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const result = errorCallback(this.reason)
        this._resolvePromise(promise, resolve, reject, result)
      }, 0)
    })
    return promise
  }

  // #endregion 私有方法

  // #region 公有方法
  then(
    successCallback = (value) => value,
    errorCallback = (err) => {
      throw err
    }
  ) {
    let promise = null

    switch (this.state) {
      case 'pending':
        promise = this._thenWhenStateIsPending(successCallback, errorCallback)
        break
      case 'fulfilled':
        promise = this._thenWhenStateIsFulfilled(successCallback)
        break
      case 'rejected':
        promise = this._thenWhenStateIsRejected(errorCallback)
        break
    }

    return promise
  }

  resolve(value) {
    return new Promise((resolve, reject) => {
      resolve(value)
    })
  }

  reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason)
    })
  }

  race(promises) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(resolve, reject)
      }
    })
  }

  all(promises) {
    // 返回一个promise实例
    return new Promise((resolve, reject) => {
      // 做一个判断参数是否是数组
      if (!Array.isArray(promises)) {
        return reject(new TypeError('arguments must be Array'))
      }

      let count = 0
      let newValues = new Array(promises.length) // 接收新的结果参数 建立一个伪数组
      for (let i = 0; i < promises.length; i++) {
        // 运用promise特性 只会有一个状态
        Promise.resolve(promises[i]).then(
          (res) => {
            count++
            newValues[i] = res // 把每次返回成功的数据添加到数组中
            if (count === promises.length) {
              // 数据接收完成
              return resolve(newValues)
            }
          },
          (rej) => reject(rej)
        )
      }
    })
  }

  // #endregion 公有方法
}

// 测试代码
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
promise
  .then(() => {
    console.log('then1')
  })
  .then(() => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 1000)
    })
  })
  .then(() => {
    console.log('then2')
  })

优雅一下

有没有感受到代码的整洁与漂亮呢,整体逻辑清晰,方法都是单一职责,容易阅读的。

小结

如果你阅读到最后,相信你一定感受到我标题所说的话,漂亮的代码会说话,通过阅读代码,就可以有效的理解它的意思。