实现一个简单的Promise

最近看了些javascript里面Promise关键字的内部实现的文章,也试着自己实现了一下Promise,在这里做一些相关记录。

Promise是一个可以在未来某个时间点返回异步操作结果的对象。这个结果可以是成功resolved解析得到的值,也可以是resovled解析过程中出错的原因。它在执行过程中有3种状态:

  • Fulfilled 完成并正确解析结果状态(调用resolve())
  • Rejected 完成但是结果解析错误状态 (调用reject())
  • Pending 既没有完成也没有拒绝

Promise内部则是通过定义一组状态机的方式来管理三个生命周期的切换,所以第一步先定义状态机的基本结构



function PromiseDemo() {
  this.PENDING = 0;
  this.FULFILLED = 1;
  this.REJECTED = 2;
  this.handlers = [];

  this.state = this.PENDING; // 初始化Promise的状态,最初的状态为PENDING
  this.value = null; // 存储状态变为FULFILLED或者REJECTED之后的值

  const fulfilled = (result) => {
    this.state = this.FULFILLED;
    this.value = result;
  };

  const rejected = (error) => {
    this.state = this.REJECTED;
    this.value = error;
  };

}

现在已经定义好了Promise的状态机,对于Promise来说,当它的状态不是pending的时候说明它已经处于完成状态(已经被resolve或者rejected了)。一旦Promise的状态从pending进行了切换(调用resolve或者reject),那么它将不会再被改变,这时再次调用resolve或者reject是没有效果的。这种保持完成状态的稳定性是Promise重要的一个特性。

标准Promise定义的实现是通过Promises/A+ specification

)社区制定的规范,简单概括一下Promise的实现大概需要遵从以下的规则:

  • 一个Promise是一个能够提供符合标准.then()方法的对象
  • pending状态的Promise可以过渡到fulfilled或者rejected状态
  • fulfilled或者rejected状态的Promise完成以后不能再过度到任何其他状态
  • 一旦Promise执行完成,它必须要有一个值(可能是undefined),这个值不能被改变

遵从这几个原则,在定义Promise状态机的时候便定义了它的三个状态,以及过度的到fulfilledrejected的方法fulfill()reject()

现在过渡Promise状态的方法有了,那么对于调用的人来说它是在什么地方进行更改的呢?先来看一个使用Promise的例子

const wait = () => { 
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('hello');
    },0)
  })
 }
wait().then((result) => { 
  console.log(result);  // hello
})

在wait函数中有setTimeout这样的异步操作,通过使用Promise去包装这个异步操作,然后当里面的异步操作结束之后调用resolve或者reject来获取异步操作结束后的值。所以这里是通过实例化Promise时传入的回调来触发状态更改的。现在来给传入的这个回调定义resolve和reject

/**
*@params { fn } promise 实例化传入的回调用来接收resolve和reject
*/
function PromiseDemo(fn) {
  this.PENDING = 0;
  this.FULFILLED = 1;
  this.REJECTED = 2;
  this.handlers = []; // 存放.then里面的success或者failure的异步操作

  this.state = PENDING; // 初始化Promise的状态,最初的状态为PENDING
  this.value = null; // 存储状态变为FULFILLED或者REJECTED之后的值

/**
* 执行.then()两个onFulfilled和onRejected异步处理操作的执行函数
*/
  const handler = (handle) => {
    if (this.state === this.PENDING) {
      this.handlers.push(handle);
    } else {
      if (
        this.state === this.FULFILLED &&
        typeof handle.onFulfilled === "function"
      ) {
        handle.onFulfilled(this.value);
      }
      if (
        this.state === this.REJECTED &&
        typeof handle.onRejected === "function"
      ) {
        handle.onRejected(this.value);
      }
    }
  };

const fulfilled = (result) => {
    this.state = this.FULFILLED;
    this.value = result;
    this.handlers.forEach(handler);
    this.handlers = null;
  };

  const rejected = (error) => {
    this.state = this.REJECTED;
    this.value = error;
    this.handlers.forEach(handler);
    this.handlers = null;
  };

function resolve(value) {// fulfill执行过程中如果发生错误,则需要切换到REJECTED状态
  try {
    fulfilled(value);
  }catch(err) {
    rejected(err);
}
}

// 传入外部调用Promise时的resolve和reject方法
fn( (result) => {
      try {
        fulfilled(result);
      } catch (error) {
        rejected(error);
      }
    },
    (error) => {
      rejected(error);
    })

this.done = (onFulfilled, onRejected) => {
// 确保结果处理操作和Promise内部的异步操作保持异步
  setTimeout(() => {
    handler({ onFulfilled,onRejected })
}, 0)
}

这里用setTimeout是为了保证Promise内部的操作都是异步,也就是说当我们在Promise内部传入的没有异步操作时也能保证它异步输出,但是一般没有异步操作也不会使用Promise

这里实现的.done方法主要是给.then方法使用。.then做的事情和.done是一样的,都是为了输出异步操作的结果,只是.then在执行的时候会在进程里面重新构造一个Promise。我们通常会这样调用.then

promise.then(
  onFulfilled?: Function,
  onRejected?: Function
) => Promise

根据Promises/A+的实现,.then需要遵从以下规则:

  • onFulfilled()onRejected()是可选参数
  • 如果onFulfilled或者onRejected()不是函数,它们将会被忽略掉
  • onFulfilled()会在Promise的状态为fulfilled的时候调用,并使用Promsie异步操作的value作为它的第一个参数,它不能在Promise还没有过渡到fulfilled状态之前被调用
  • onRejected()会在Promise的状态为rejected的时候调用,并使用过渡到rejected的异常原因作为第一个参数,它不能在Promise还没有过渡到rejected状态之前被调用
  • onFulfilled()onRejected()都不能调用超过一次
  • .then()可以在一个Promise里面被调用很多次,当Promise处于fulfilled状态,所有各自的onFullfilled()回调都必须按照它们在.then调用里面的顺序执行。同样当Promise处于rejected状态,所有各自的onRejected()回调必须按照它们在.then调用里面的顺序执行
  • .then()必须要返回一个Promise
    Promise2 = Promise1.then(onFulfilled, onRejected)
    
    • 如果onFulfilled()或者onRejected()返回一个x值,并且x是一个Promise, 那么Promise2将会和x的状态以及value保持一致,否则Promise2会以x的值切换到fulfilled状态
    • 如果onFulfilled()或者onRejected()抛出一个异常e,promise2必须过渡到rejected状态,并且使用e作为理由
    • 如果onFulfilled()不是一个函数并且promise1过渡到fulfilled状态,promise2必须要使用和promise1同样的值过渡到fulfilled状态
  • 如果onRejected()不是一个函数并且promise1过渡到rejected状态,promise2必须要使用和promise1同样的异常原因过渡到rejected状态

遵从以上.then()的原则,现在先来简单实现一下.then()

this.then = (onFulfilled, onRejected) => {
  return new Promise((resolve, reject) => {
      this.done((result) => {
        if(typeof onFulfilled === 'function') {
        // 这里使用这么多的回调是为了在结果处理的回调里面能够拿到异步操作的结果值
            try {
             resolve(onFulfilled(result));
          }catch(err) {
             reject(err);
          }
      }else {
         resolve(result);
      }
    }, (err) => {
        if(typeof onRejected === 'function') {
            try {
             resolve(onRejected(err));
            }catch(ex) {
              reject(ex);
          }  
        }else {
            reject(err);
        }
    })
  })
}

上面的Promise还有一个地方没有实现,就是在.then里面有新的异步操作,又使用Promise去包装操作之后,在下一个.then()里面需要接收到这个异步操作的结果。也就是上面提到的.then()原则的倒数第二条。首先需要将fn()的调用封装一下,同时需要添加一个getThen()的辅助函数用于获取.then()里面的异步操作

 /**
   * 检查value值是不是Promise,如果是的话返回这个promise的.then方法
   *
   */
  const getThen = (value) => {
    const t = typeof value;
    if (t && (t === "object" || t === "function")) {
      const then = value.then;
      if (typeof then === "function") {
        console.log('functionThen', then);
        return then;
      }
    }
    return null;
  };

const doResolve = (fn, onFulfilled, onRejected) => {
    try {
      fn(
        (result) => {
          try {
            onFulfilled(result);
          } catch (error) {
            onRejected(error);
          }
        },
        (error) => {
          onRejected(error);
        }
      );
    } catch (error) {
      onRejected(error);
    }
  };

   const resolve = (result) => {
    try {
      const then = getThen(result);
      if (then) {
        doResolve(then, resolve, rejected);
        return;
      }
      fulfilled(result);
    } catch (error) {
      rejected(error);
    }
  };

完整代码实现:

function MyPromise(fn) {
  this.PENDING = 0;
  this.FULFILLED = 1;
  this.REJECTED = 2;
  this.handlers = [];

  this.state = this.PENDING;
  this.value = null;

  const handler = (handle) => {
    if (this.state === this.PENDING) {
      this.handlers.push(handle);
    } else {
      if (
        this.state === this.FULFILLED &&
        typeof handle.onFulfilled === "function"
      ) {
        handle.onFulfilled(this.value);
      }
      if (
        this.state === this.REJECTED &&
        typeof handle.onRejected === "function"
      ) {
        handle.onRejected(this.value);
      }
    }
  };

  const fulfilled = (result) => {
    this.state = this.FULFILLED;
    this.value = result;
    this.handlers.forEach(handler);
    this.handlers = null;
  };

  const rejected = (error) => {
    this.state = this.REJECTED;
    this.value = error;
    this.handlers.forEach(handler);
    this.handlers = null;
  };

  /**
   * 检查value值是不是Promise,如果是的话返回这个promise的.then方法
   *
   */
  const getThen = (value) => {
    const t = typeof value;
    if (t && (t === "object" || t === "function")) {
      const then = value.then;
      if (typeof then === "function") {
        console.log('functionThen', then);
        return then;
      }
    }
    return null;
  };

  const doResolve = (fn, onFulfilled, onRejected) => {
    try {
      fn(
        (result) => {
          try {
            onFulfilled(result);
          } catch (error) {
            onRejected(error);
          }
        },
        (error) => {
          onRejected(error);
        }
      );
    } catch (error) {
      onRejected(error);
    }
  };

  const resolve = (result) => {
    try {
      const then = getThen(result);
      if (then) {
        doResolve(then, resolve, rejected);
        return;
      }
      fulfilled(result);
    } catch (error) {
      rejected(error);
    }
  };

  doResolve(fn, resolve, rejected);

  this.done = (onFulfilled, onRejected) => {
    // 确保promise内部的操作都是异步
    setTimeout(() => {
      handler({ onFulfilled, onRejected });
    }, 0);
  };

  this.then = (onFulfilled, onRejected) => {
    return new MyPromise((resolve, reject) => {
      return this.done(
        (value) => {
          if (typeof onFulfilled === "function") {
            // 这里使用这么多的回调是为了在结果处理的回调里面能够拿到异步操作的结果值
            try {
              return resolve(onFulfilled(value));
            } catch (error) {
              return reject(error);
            }
          } else {
            return resolve(value);
          }
        },
        (error) => {
          if (typeof onRejected === "function") {
            try {
              return resolve(onRejected(error));
            } catch (error) {
              return reject(error);
            }
          } else {
            return reject(error);
          }
        }
      );
    });
  };
}

exports.myPromise = MyPromise;

总结

  • Promise内部通过定义状态机来实现pending, fulfilled, rejected三中状态的过渡。pending状态为处理内部异步操作,fulfilled和rejected为异步操作结束后输出处理成功的结果,以及失败的原因
  • Promise需要具有符合PromiseA+规范的标准.then()方法,用于对Promise异步输出的结果进行处理。

参考文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352