如何创建自己的 Promise

有没有想过 Promise 在 JavaScript 中是如何工作的? 让我们实现一个,看看我们是否能更好地理解它。


什么是 Promise?

Promise 是一个可以表示异步操作的对象。

该对象承诺返回此异步操作的结果,无论是成功还是失败。 如果此操作从未完成,它将一直等到永恒或直到它被垃圾收集。

我们需要一个媒介来从 Promise 中收集这个异步操作的结果,这就是 then() 方法成为众人瞩目的地方。

Thenable

thenable 是任何具有 then() 方法实现的对象。 是的,Promise 是可以的。

在 Promise 上下文中,then() 方法用于添加处理程序,这些处理程序最终将接收它所表示的异步操作的结果。

Promise
.resolve( 'result' )
.then( successcallback, failurecallback );

完成后的操作会导致 promise 以已解决的值或被拒绝的错误解决。

如果操作成功完成,将触发 successcallback(value) ,并将解析的值作为参数传递。

如果操作严重失败,则触发 failurecallback(error) 并将被拒绝的错误作为参数传递。

因此,Promise 只能具有三种状态来表示异步操作,即:

  • pending:等待异步操作完成。
  • fulfilled:操作已成功完成。
  • rejected:操作以某种方式失败。

当一个 Promise 被实现或被拒绝时,它就成为了 Promise 的最终状态,从而使 Promise 得到了解决,从此幸福地…

一旦 Promise 被解决,就不能对 Promise 的结果或状态进行进一步的更新。


Promise 构造函数:

Promise 构造函数为我们创建了 Promise 对象:

new Promise( executor )

它需要一个将异步活动与 Promise 联系起来的 executor 函数。

这个 executor 函数由 Promise 构造函数调用,参数 resolve 和 reject 是 Promise 构造函数提供的方法,用于执行以下操作:

  • resolve(data) :将 Promise 状态设置为已完成,并将结果设置为 data 。
  • reject(error) : 将 Promise 状态设置为被拒绝,并将结果设置为 error。

基于异步活动,executor 函数可以调用 resolve 或 reject 方法将结果传递给 Promise。

一个 Promise 一旦被构造,就具有内部属性:

  • [[PromiseState]] : promise 的当前状态。
  • [[PromiseResult]] :保存已解决的值或被拒绝的原因。

让我们看一个例子:

const error = null ; 
//const error = 'error' ; // 更改为真实值以拒绝 promise。
const promise = new Promise( ( resolve, reject ) => {
    // 异步操作
    setTimeout( () => {
       return error ? reject( error ) : resolve( 'final data' ) ;
    }, 3000 );
} );

// before 3 seconds:
Promise {<pending>} 
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined

//resolved after 3 seconds :
Promise {<fulfilled>: 'final data'}
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "final data"
         
//rejected after 3 seconds :
Promise {<rejected>: 'error'}
[[PromiseState]]: "rejected"
[[PromiseResult]]: "error"    

创建一个 PromiseCopy:

让我们从构造函数开始。 我们收到 executor ,我们需要传递实用函数的 resolve 和 reject 方法。

resolve 和 reject 是 Promise 中的静态方法,用于创建新的 Promise,如下所示:

Promise.resolve( 'create a resolved promise' );
//Promise {<fulfilled>: 'create a resolved promise'}
Promise.reject( 'create a rejected promise' );
//Promise {<rejected>: 'create a rejected promise'}

当这些方法被传递给 Promise 构造函数中的 executor 函数时,它们的行为会有所不同。 它们绑定到 Promise 实例并返回 undefined 。

有了以上知识,让我们继续创建一个 PromiseCopy:

const states = {
  pending: 'PENDING',
  fulfilled: 'FULFILLED',
  rejected: 'REJECTED'
}
class PromiseCopy{
  #state = states.pending // initial state of promise
  #result = undefined ; 
  
   constructor( executor ){
    
     try{
        executor( PromiseCopy.resolve.bind( this ), PromiseCopy.reject.bind( this ) )
     }
     catch( e ){
        PromiseCopy.reject.call( this, e ) ; // reject promise when error thrown.
     }
    
   }
  
  static resolve(){ /**/ }
  static reject(){ /**/ }
}

Promise.resolve():

Promise.resolve(value) 接收成功响应,该响应设置为 Promise 的#result

但是如果这个值是一个 thenable 对象,Promise.resolve() 不会把这个值设置为#result

如果将 thenable 对象传递给 Promise.resolve(),它将通过递归调用 Promise.resolve() 来继续解析结果,直到它达到最终值。

一旦 Promise.resolve() 收到最终值,它就会更新 Promise 的 #result 并将状态设置为已完成。

一旦 Promise 完成,我们就可以调用回调函数,急切地等待#result。 让我们牢记以上几点来编写 resolve() 方法:

class PromiseCopy{

  static resolve( value ){
     /* 
        1. 如果直接调用 Promise.resolve,我们返回一个新的 Promise 并将值传递给 resolve 方法。
        2. executor 的 resolve 方法在添加此实例的情况下运行相同的代码。
     */ 
    if( !( this instanceof PromiseCopy ) ) return new PromiseCopy( res => res( value ) ) ; 
    
    // 如果 promise 已解决,则返回。
    if( this.#state !== states.pending )return ;
 
    // 如果 value 是 thenable 对象,我们通过将其传递给 value.then 递归调用 Promise.resolve。
    if( isThenable( value ) ) return value.then( PromiseCopy.resolve.bind( this ), PromiseCopy.resolve.bind( this ) ); 
      
 
    this.#state = states.fulfilled ;
    this.#result = value ;

    this.#dispatchCallbacks();
    
  }
  
  #dispatchCallbacks(){ /* */ } ;

}

Promise.reject():

reject(reason) 方法与 resolve() 方法非常相似,只是它不对 thenable 进行任何解包。 它直接前进并在#result 中设置错误原因并将#state 设置为 rejected。 另外,是的,调度回调:

class PromiseCopy{

  static reject( reason ){
    // 如果直接调用 Promise.reject,我们返回一个新的 Promise 并将原因传递给 reject 方法。
    if( !( this instanceof PromiseCopy ) ) return new PromiseCopy( ( _ , rej ) => rej( reason ) ) ; 
    
    // 如果 promise 已解决,则返回。
    if( this.#state !== states.pending )return ;
      
    this.#state = states.rejected ;
    this.#result = reason ;

    this.#dispatchCallbacks();
    
  }
  
  #dispatchCallbacks(){ /* */ } ;

}

then() 和 Promise 链接:

一个 Promise then( successcallback, failurecallback ) 用于添加回调。这些回调存储在 Promise 实例中,并在 Promise 完成后触发。

Promise.then() 也是可组合的,这意味着我们可以链接 then() 调用,并且每个 then() 方法都会接收上一个异步操作的结果。

这对于避免回调地狱特别有用,因为可以通过 Promise 链接来链接依赖的异步任务。

then( successcallback, failurecallback ) 总是返回一个新的 Promise,该 Promise 由 successcallback 或 failurecallback 的返回值解决,具体取决于当前 Promise 的#state

如果 successcallback 或 failurecallback 返回一个 Promise,则在执行 Promise 链中的下一个 then() 方法之前,结果会被新 Promise 的 resolve() 方法解包。

另一个重要的事情是,如果这些回调没有通过,当前的 Promise #state 和 #result 会被转发到新的 Promise。

让我们看一下 then() 的实现:

class PromiseCopy{
    
    then( successcallback , failurecallback ){
        return new PromiseCopy( ( resolve, reject ) => { 
            /** 
              1. handlers 是一个数组,它将存储成功处理程序和错误处理程序。
              2. 每个条目将具有:
                - 使用resolve值调用的 success 方法。
                - 使用rejected值调用的 failure 方法。
            */
            this.#handlers.push( {
                success( value ){
                    if( !successcallback )return resolve( value );

                    try{
                        resolve( successcallback( value ) );
                    }
                    catch( e ){
                        reject( e )
                    }
                },
                fail( error ){
                    if( !failurecallback )return reject( error );

                    try{
                        resolve( failurecallback( error ) );
                    }
                    catch( e ){
                        reject( e )
                    }
                }
             } );

           // 如果 promise 已经解决,则执行处理程序。
           this.#dispatchCallbacks(); 
        });
    }
}

请注意上面有一个条目被推送到handlers,当当前的 Promise 被结算时将被触发。


Handlers

Handlers 是一个数组,其中包含使用 then() 方法附加的回调。 一旦 Promise 被解决,这些处理程序就会被引用并异步触发。

每个处理程序包含方法:

  • success() :当 Promise 完成时执行。
  • fail() : 当 Promise 被拒绝时执行。

我们可以创建一个 #dispatchCallback() 方法来检查 Promise 是否已结算并最终调用处理程序。

每个处理程序只应在承诺完成后触发一次,之后应从引用中删除。

由于 Promise 处理是在微任务队列中完成的,我们可以使用 queueMicroTask 方法将处理程序作为微任务异步触发。

我们还将有一个私有属性#queued 作为检查以避免重复触发器:

class PromiseCopy{
  #handlers = [] ;
  #queued = false ;
  
  #dispatchCallbacks(){
      if( this.#state === states.pending )return ;
      if( this.#queued )return ;

      const method = this.#state === states.fulfilled ? 'success' : 'fail' ; //handler method to call

      queueMicrotask( () => {
          // 如果 promise 被拒绝并且没有可用的处理程序,则记录 promise 错误。
          if( this.#state === states.rejected && !this.#handlers.length ) console.error( 'Uncaught (in promisecopy)', this.#result );
          
          for( let handler of this.#handlers ){
            handler[ method ]( this.#result ) ;
          }
          //cleanup activities:
          this.#handlers = []; //remove triggered handlers.
          this.#queued = false ;
      } );
    
      this.#queued = true ;

  }

}

Log Promise 错误

如果未提供失败回调来处理错误,则 Promise if denied 会记录一个通用错误。

尽管在 Promise 链中,被拒绝的 Promise 将由处理程序转发,直到它到达没有 #handlers 的 Promise。 这本质上可以是我们在#dispatchCallback() 中触发错误日志的检查。


catch():

我们也可以使用 catch() 方法附加失败回调。 但是,如果 promise 得到满足,我们还需要将该值转发给下一个 then() 方法。

为此,我们可以重用 then() 方法并将 undefined 作为成功回调传递。 相当简单的权利:

class PromiseCopy{
  
  catch( failurecallback ){
        return this.then( undefined, failurecallback ) ;
  }
  
}

finally():

finally( finallycallback ) 方法无论 Promise 被实现还是被拒绝都会被实现。 但这并不像将 finallycallback 传递给 then( finallycallback, finallycallback ) 那样简单。

finally() 的特殊之处在于当前 Promise 的#result 没有传递给 finally 回调。 当前 Promise 的 #state 和 #result 被传递给 finally() 返回的新 Promise。

如果 finallycallback 是异步的,也就是说如果它返回一个 Promise 或 thenable,那么 Promise 链中的下一个回调只有在这个操作完成后才会被触发。

在某种程度上,finallycallback 只是一些在必须成功的 Promise 链之间做的中间任务。

因为如果 finallycallback 有错误,finally() 返回的新 Promise 会因为这个错误而被拒绝。

让我们看一下实现:

class PromiseCopy{  
  
  finally( callback ){
    // resolveOrReject : forward current #state.
    // value : forward current #result.
    
    /** curried function to pass Promise.resolve or 
        Promise.reject forward based on current Promise #state. **/
    const commonCallback = ( resolveOrReject ) => ( value ) => {
      const response = callback?.() ;
      return Promise.resolve( response ).then( _ => resolveOrReject( value ) );
    } 
    return this.then( commonCallback( Promise.resolve ) , commonCallback( Promise.reject ) );
  }
}

总结

在 JavaScript 中实现异步任务时,了解 Promise 的工作原理会非常有益。 我希望这篇文章可以帮助你创建自己的 Promise,然后异步等待它。