博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自己手撸一个符合Promise/A+的Promise
阅读量:5926 次
发布时间:2019-06-19

本文共 5830 字,大约阅读时间需要 19 分钟。

简单介绍

Promise是什么,相信不用说了,写过js的人或多或少都接触过。刚开始用Promise的时候,总感觉这种写法非常的怪异,但是当慢慢熟悉的时候,发现一切都是那么和谐。

我自己理解的Promise用来解决异步回调嵌套的问题,它代表了异步操作的一种结果。它是一种状态机,从实现上来说一种回调包装器(自己的理解看法如有异议,欢迎讨论)。

当真正看了下规范和看了别人的实现代码后,发现Promise的原理和实现也不是很难,所以就有了这篇文章,旨在自我学习,可能也能顺手帮到一些人。这是最后实现的和。我会频繁更新学习深入现代js框架的文章, 欢迎star。

Promise规范

Promise在ES6出现之前,就已经有了各个版本的"Promise",但是当这些用在一起的时候,如果没有一个统一的实现标准,那将是灾难性的。所以就有了规范,Promise/A+就是Promise目前最新的规范。规范旨在帮助实现者指定要达到的目标,并不是规定如何实现。当写完自己的Promise后,要确定是否符合Promise/A+规范,可以使用来进行测试。

原理和实现

先来想想Promise的实现应该是个什么样的过程。Promise把我们异步的回调函数进行了封装隐藏,让我们可以在then函数以链式的写法在里面写回调和错误处理,所以按照正常思路就是我们应该将then里的回调函数进行存储,然后等异步执行完后,我们由Promise自行调用。

首先是Promise的构造函数

function Promise (executor) {  // 为了方面后面函数调用,防止this被破坏  var self = this;  this.status = 'pending';  this.value = null;  this.reason = null;  // resolve的回调队列  this.resolveCbs = [];  // reject的回调队列  this.rejectCbs = [];  // resolve的时候改变状态,保存好传入的值,并调用相应的回调队列  function resolve(value) {    if (self.status === 'pending') {      // 由于promise需要异步执行,这里使用setTimeout来延迟执行      setTimeout(function() {        self.status = 'fulfilled';        self.value = value;        self.resolveCbs.forEach(cb => cb(value));      });    }  }  /// 与resolve相似,不过这里保存的是原因,改变状态为rejected  function reject(reason) {    if (self.status = 'pending') {      setTimeout(function() {        self.status = 'rejected';        self.reason = reason;        self.rejectCbs.forEach(cb => cb(reason));      });    }  }  executor(resolve, reject);}复制代码

Promise有三种状态,分别是pendingfulfilledrejected。它的状态可以由pending转为fulfilled,由pending转为rejected,状态一旦确定将无法更改。

有了构造器,下面我们来实现Promise中的关键函数,then函数。为了实现链式调用,我们需要返回一个Promise对象,但是我们不能返回自己,因为Promise的状态不可更改,我们需要返回一个新的Promise对象。基本解释都包含在了注释中

Promise.prototype.then = function (onResolved, onRejected) {  var self = this;  var promise = null;  // onResolved是可选的,当其不存在或不是函数时,将其接受到的值一次往后透传  onResolved = typeof onResolved === 'function' ? onResolved : function (value) { return value; };  // onRejected是可选的,当其不存在或不是函数时,将其错误继续向后抛  onRejected = typeof onRejected === 'function' ? onRejected : function (error) { throw error; };  // 新的promise状态需要根据x的具体情况来确定  function resolvePromise(promise, x, resolve, reject) {    // 这一部分属于Promise/A+规范的Resolution Procedure部分    // 2.3.1: 如果promise对象和x引用的是同一个对象,那么应该用一个TypeError的错误来reject掉promise    // 如果两个对象是同一个对象,那么会无限循环调用,会出现错误    if (promise === x) {      return reject(new TypeError('Chaining cycle detected for promise!'));    }    // 2.3.2: 如果x是一个promise,应该用以下这些来决定它的状态    if (x instanceof Promise) {      // 2.3.2.1: 如果x是pending状态,那么promise必须是pending状态,直到x是fulfillded或rejected状态      // 2.3.2.2: 如果x是fulfilled状态,那么promise需要用相同的值来resolve      // 2.3.2.3: 如果x是rejected状态,那么promise需要用相同的原因来reject      if (x.status === 'pending') {        x.then(function(value) {          // 由于x可能还是一个promise,所以这里递归调用          resolvePromise(promise, value, resolve, reject);        }, reject);      } else {        x.then(resolve, reject);      }      return;    }    // 2.3.3: 如果x是一个对象或者函数,这里是出里thenable的情况,thenable是指具有then函数的对象或函数    // 2.3.4: 如果x既不是对象也不是函数,那么直接使用x来resolve promise    if ((x !== null && typeof x === 'object') || typeof x === 'function') {      var isCalled = false;      try {        // 2.3.3.1: 将x.then赋值为then        var then = x.then;        // 2.3.3.2: 如果检索到x.then的结果抛出了错误,那么直接reject掉        // 2.3.3.3: 如果then是一个函数,那么用x作为this,第一个参数是resolvePromise,第二个参数是rejectPromise        if (typeof then === 'function') {          // 2.3.3.3.1: 如果resolvePromise被使用一个参数值y调用,执行[[Resolve]](promise, y)          // 2.3.3.3.2: 如果rejectPromise被使用一个原因r调用,使用r来reject promise          then.call(x, function (y) {            // 2.3.3.3.3: 如果resolvePromise和rejectPromise同时被调用,或者这两个函数被使用相同的参数多次调用,那么只执行最开始的,其他的全部忽略            if (isCalled) return;            isCalled = true;            return resolvePromise(promise, y, resolve, reject);          }, function (r) {            if (isCalled) return;            isCalled = true;            return reject(r);          });        } else {          // 2.3.3.4: 如果then不是函数,用x来resolve promise          resolve(x);        }      } catch(err) {        // 2.3.3.3.4: 如果调用then的时候抛出错误        // 2.3.3.3.4.1: 如果resolvePromise和rejectPromise已经被调用了,那么直接忽略掉        // 2.3.3.3.4.2: 否则使用err来reject promise        if (isCalled) return;        isCalled = true;        reject(err);      }    } else {      resolve(x);    }  }  function handlePromise(modifier, resolve, reject) {    return function (value) {      setTimeout(function() {        try {          var x = modifier(value);          resolvePromise(promise, x, resolve, reject);        } catch(err) {          reject(err)        }      });    }  }  if (self.status === 'fulfilled') {    promise = new Promise(function (resolve, reject) {      handlePromise(onResolved, resolve, reject)(self.value);    });  } else if (self.status === 'rejected') {    promise = new Promise(function (resolve, reject) {      handlePromise(onRejected, resolve, reject)(self.reason);    });  } else {    promise = new Promise(function (resolve, reject) {      self.resolveCbs.push(handlePromise(onResolved, resolve, reject));      self.rejectCbs.push(handlePromise(onRejected, resolve, reject));    });  }  return promise;}复制代码

到了这里我们的Promise基本已经实现完成。下面让我们来测试我们的Promise是否符合Promise/A+规范。

测试

这里我们使用进行测试,用它进行测试要先实现一个适配器。下面我们按照它的要求来实现一个

Promise.deferred = function () {  var global = {};  var promise = new Promise(function (onResolve, onReject) {    global.onResolve = onResolve;    global.onReject = onReject;  });  var resolve = function (value) {    global.onResolve(value);  };  var reject = function (reason) {    global.onReject(reason);  }  return {    promise,    resolve,    reject  }}复制代码

然后导出我们的Promise

module.exports = Promise;复制代码

调用我们的Promise进行测试

var promisesAplusTests = require('promises-aplus-tests');var adapter = require('./promise');promisesAplusTests(adapter);复制代码

最后不考虑性能问题,我们的Promise全部通过Promise/A+测试。

其他文章

转载地址:http://olavx.baihongyu.com/

你可能感兴趣的文章
从0到1简易区块链开发手册V0.3-数据持久化与创世区块
查看>>
12C RAC for ASM添加磁盘步骤
查看>>
归档日志 delete input 和delete all input 区别
查看>>
最难面试IT公司的测试题
查看>>
Windows获取CPU、内存和磁盘使用率脚本
查看>>
Linux优化之IO子系统监控与调优
查看>>
Upgrade Oracle GI 11.2.0.4_to_12.1.0.2
查看>>
使用ansible 批量分发SSH Key
查看>>
安装MHA中清理Relay log报错
查看>>
***测试工具
查看>>
聚类算法学习指南(二)
查看>>
秒级定时计划
查看>>
crontab执行shell脚本日志中出现乱码
查看>>
依赖Zookeeper生成全局唯一序列号
查看>>
学习韩立刚老师IT运维课程,成为韩立刚老师正式学生,在全国范围为你就近推荐工作。...
查看>>
理解流量监管和整形的关键算法—令牌桶
查看>>
演示: 动态NAT完成网络地址翻译
查看>>
K8S集群Master高可用实践
查看>>
CocoStudio游戏发布后资源加密大致实现思路
查看>>
妙用SQL Server聚合函数和子查询迭代求和
查看>>