阅读 1368

从Promise规范到手写Promise

本文档已更新于 【前端橘子君】 【Github】

promise就是将异步任务队列化,将多个异步任务按照顺序输出,同时用链式调用解决回调地狱的问题。

文章尾部附上完整源码

用法

var p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('我是异步返回的数据')
  }, 1000);
});

p.then(data => {
  console.log(data);
});
复制代码

promise规范

  • 存在三个状态:等待态(pending),执行态(fulfilled),失败态(rejected)
  • 初始态为等待态,可以转化为执行态和失败态
  • 执行态不可转化为其他状态,且必须有一个不可变的终值(value)
  • 失败态不可转化为其他状态,且必须有一个不可变的原因(reason)
  • 必须提供一个then方法,以供访问其当前值,终值及原因
  • then方法提供两个参数:onFulfilledonRejected
  • onFulfilledonRejected如果不是函数类型,必须忽略其
  • 如果executor执行报错,直接执行reject
  • 不同的promise可以相互套用

更多规范细节可以参考Promise A+ 规范

规范版的初始版本

// 依据规范生成初始版本
class NewPromise {
  constructor (executor) {
    // 存在三种状态
    this.enumStatus = {
      PADDING: 'padding',
      FULFILLED: 'fulfilled',
      REJECTED: 'rejected'
    };
    // 初始态为等待态
    this.state = this.enumStatus.PADDING;
    // 必须存在一个终值
    this.value = '';
    // 必须存在一个被拒绝的原因
    this.reason = '';
    // 转化执行态
    let resolve = (value) => {
      // 修改状态
      this.state = this.enumStatus.FULFILLED;
      // 必须有一个终值
      this.value = value;
    };
    // 转化失败态
    let reject = (reason) => {
      // 修改状态
      this.state = this.enumStatus.REJECTED;
      // 如果是失败态,必须有一个据因
      this.reason = reason;
    };
    // 如果executor执行报错,直接执行reject
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  // 必须包含then方法
  then (onFulfilled, onRejected) {
    // 保证onFulfilled和onRejected都是函数类型
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
		onRejected = typeof onRejected === 'function' ? onRejected : err => { Throw(err) };
    // 如果当前状态为执行态,则执行onFulfilled函数,将终值作为该函数的参数,否则忽略其
    if (this.state === this.enumStatus.FULFILLED) {
      onFulfilled(this.value);
    }
    // 如果当前状态为拒绝态,则执行onRejected函数,将据因作为该函数的参数,否则忽略其
    if (this.state === this.enumStatus.REJECTED) {
      onRejected(this.reason);
    }
  }
}
复制代码

此时的Promise已经可以实现基本的功能了,不信的话调用看看。

var p = new NewPromise((resolve, reject) => {
  resolve('前端橘子君');
});

p.then((data) => {
  console.log(data);
});
// 输出前端橘子君
复制代码

但是这个不能解决异步问题。

var p = new NewPromise((resolve, reject) => {
  // 模拟服务器返回数据
  setTimeout(() => {
    resolve('前端橘子君');
  }, 1000);
});

p.then((data) => {
  console.log(data);
});

// 不会打印任何东西
复制代码

怎么解决这个问题呢?我们可以借助发布-订阅者模式来处理。

不管在响应服务器返回数据期间then函数中做了什么,用一个队列来将then中的操作依次保存起来,等服务器的数据返回后依次执行依次不就可以了么?

解决promise异步问题

只将修改部分的代码,不再多述其他重复代码

class NewPromise {
  constructor (executor) {
    ...
    // 任务中心
    this.onFulfilledQueue = [];
    this.onRejectedQueue = [];
    // 转化执行态
    let resolve = (value) => {
      ...
      // 当服务器数据返回时,依次执行预存的成功事务
      this.onFulfilledQueue.map(fn => fn(this.value));
    };
    // 转化失败态
    let reject = (reason) => {
      ...
      // 当服务器数据返回时,依次执行预存的失败事务
      this.onRejectedQueue.map(fn => fn(this.reason));
    };
    ...
  }
  then (onFulfilled, onRejected) {
    ...
    // 如果当前状态为等待态,则将其加入事务
    if (this.state === this.enumStatus.PADDING) {
      this.onFulfilledQueue.push(() => {
        onFulfilled(this.value);
      });
      this.onRejectedQueue.push(()=> {
        onRejected(this.reason);
      });
    }
  }
}
复制代码

再执行一下上面的操作,肯定会输出你想要的结果。

还有两个问题:

  • 1、既然可以链式调用,则说明其返回的是一个Promise对象,new 一个作为结果不就可以了么。
  • 2、链式调用,如果用户将自身作为结果返回,会造成死循环,所以我们需要对其进行一些比较处理
  • 3、其他(详情参考Promise A+ 规范)

解决方案

  • 如果promisex相等,则拒绝执行promise
  • 如果x是一个对象或函数,且其有then属性是一个函数,则以x作为作用域执行then函数
  • 如果then执行后返回的值仍然是一个promise,则递归调用resolvePromise
  • 否则以x为参数执行promise
  • 如果x不是一个对象或函数,则以x为参数执行promise
/**
 * @param promise2 新的promise
 * @param x 原始的promise
 * @param resolve promise2的resolve
 * @param reject promise2的reject
*/
function resolvePromise(promise2, x, resolve, reject) {
  // 防止循环调用
  if (promise2 === x) {
    reject(Throw('不允许循环调用'));
  }
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    //函数或对象
    let then = x.then;
    if (typeof then === 'function') {
      // 如果函数运行后的返回值仍然是一个promise,则递归调用resolvePromise
      then.call(x, res => resolvePromise(promise2, res, resolve, reject), err => reject(err));
    }else {
      resolve(x);
    }
  } else {
    //普通值
    resolve(x)
  }
}
复制代码

改造then方法

通过了解决方案,我们接着在then方法中使用resolvePromise

class NewPromise {
  ...
  then (onFulfilled, onRejected) {
    // 保证onFulfilled和onRejected都是函数类型
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { Throw(err) };
    let promise2 = new NewPromise((resolve, reject) => {
      // 如果onFulfilled是函数类型,且状态为执行态,则执行该函数,将终值作为该函数的参数,否则忽略其
      if (this.state === this.enumStatus.FULFILLED) {
        setTimeout(() => {
          resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
        }, 0);
      }
      // 如果onRejected是函数类型,且状态为拒绝态,则执行该函数,将据因作为该函数的参数,否则忽略其
      if (this.state === this.enumStatus.REJECTED) {
        setTimeout(() => {
          resolvePromise(promise2, onRejected(this.reason), resolve, reject);
        }, 0);
      }
      // 如果当前状态为等待态,则将其加入事务
      if (this.state === this.enumStatus.PADDING) {
        this.onFulfilledQueue.push(() => {
          resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
        });
        this.onRejectedQueue.push(()=> {
          resolvePromise(promise2, onRejected(this.reason), resolve, reject);
        });
      }
    });
    return promise2;
  }
}
复制代码

其实对then方法的改造并没有那么多,只是将原本调用的地方用resolvePromise替换了而已,很多人会说为什么会有一个setTimeout呢?

如果仔细看过规范的话里边涉及到一条,then 方法可以被同一个 promise 调用多次

返回的promise是一个异步对象,如果不使用setTimeout,就有可能只执行第一个then,后边的就不会执行了,这点请自行验证。

调用试试

var p1 = new NewPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('前端橘子君');
  }, 3000);
});

var p2 = new NewPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('我是链式调用的结果')
  }, 3000);
})

p1.then((data) => {
  console.log(data);
  return p2;
}).then(data => {
  console.log(data);
});
复制代码

至此,promise才算真正的完成了。

本案例不是完整代码,因为该案例还有多个地方没有设计的,比如,promise.allpromise.resolve, promise.reject, promise.race,所以本文仅供学习参考使用

案例完整代码

// 依据规范生成初始版本
class NewPromise {
  constructor (executor) {
    // 存在三种状态
    this.enumStatus = {
      PADDING: 'padding',
      FULFILLED: 'fulfilled',
      REJECTED: 'rejected'
    };
    // 初始态为等待态
    this.state = this.enumStatus.PADDING;
    // 必须存在一个终值
    this.value = '';
    // 必须存在一个被拒绝的原因
    this.reason = '';
    // 任务中心
    this.onFulfilledQueue = [];
    this.onRejectedQueue = [];
    // 转化执行态
    let resolve = (value) => {
      // 修改状态
      this.state = this.enumStatus.FULFILLED;
      // 必须有一个终值
      this.value = value;
      // 当服务器数据返回时,依次执行预存的成功事务
      this.onFulfilledQueue.map(fn => fn(this.value));
    };
    // 转化失败态
    let reject = (reason) => {
      // 修改状态
      this.state = this.enumStatus.REJECTED;
      // 如果是失败态,必须有一个据因
      this.reason = reason;
      // 当服务器数据返回时,依次执行预存的失败事务
      this.onRejectedQueue.map(fn => fn(this.reason));
    };
    // 如果executor执行报错,直接执行reject
    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  // 必须包含then方法
  then (onFulfilled, onRejected) {
    // 保证onFulfilled和onRejected都是函数类型
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { Throw(err) };
    let promise2 = new NewPromise((resolve, reject) => {
      // 如果onFulfilled是函数类型,且状态为执行态,则执行该函数,将终值作为该函数的参数,否则忽略其
      if (this.state === this.enumStatus.FULFILLED) {
        setTimeout(() => {
          resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
        }, 0);
      }
      // 如果onRejected是函数类型,且状态为拒绝态,则执行该函数,将据因作为该函数的参数,否则忽略其
      if (this.state === this.enumStatus.REJECTED) {
        setTimeout(() => {
          resolvePromise(promise2, onRejected(this.reason), resolve, reject);
        }, 0);
      }
      // 如果当前状态为等待态,则将其加入事务
      if (this.state === this.enumStatus.PADDING) {
        this.onFulfilledQueue.push(() => {
          resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
        });
        this.onRejectedQueue.push(()=> {
          resolvePromise(promise2, onRejected(this.reason), resolve, reject);
        });
      }
    });
    return promise2;
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  // 防止循环调用
  if (promise2 === x) {
    reject(Throw('不允许循环调用'));
  }
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    //函数或对象
    let then = x.then;
    if (typeof then === 'function') {
      // 如果函数运行后的返回值仍然是一个promise,则递归调用resolvePromise
      then.call(x, res => resolvePromise(promise2, res, resolve, reject), err => reject(err));
    }else {
      resolve(x);
    }
  } else {
    //普通值
    resolve(x)
  }
}

// 调用
var p1 = new NewPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('前端橘子君');
  }, 1000);
});

var p2 = new NewPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('我是链式调用的结果')
  }, 3000);
})

p1.then((data) => {
  console.log(data);
  return p2;
}).then(data => {
  console.log(data);
});
复制代码

更多相关文档,请见:

线上地址 【前端橘子君】

GitHub仓库【前端橘子君】

文章分类
前端
文章标签