天天看点

前端实现一个异步的promise参考文档分析具体实现代码

参考文档

面试经常被问道怎么实现Promise,下面我们来实现一个Promise。我在网上搜索了一堆实现方案,总感觉有些不符合我的期望,下面是我的一些参考的实现方案:

1. https://www.jianshu.com/p/43de678e918a

2. https://segmentfault.com/a/1190000016550260

MDN上的Promise文档:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

前端实现一个异步的promise参考文档分析具体实现代码

在这里我只实现了Promse的then,catch方法,其余的api如all, resolve,any,race等,感兴趣的小伙伴们可以去尝试去实现,这是我的项目代码:

https://codesandbox.io/s/youthful-paper-6vwfy?file=/src/myPromise.js

上面附带了测试案例,可以直接线上运行无需下载,大家有兴趣可以试一试:

https://codesandbox.io/s/youthful-paper-6vwfy?file=/src/TestMyPromise.js

分析

首先我们来定义一下Promise的接口有那些:

class MyPromise{
	/**
	* 动态生成的,根据then的回调函数生成,在构造函数中初始化,
	* 初始化的值为undefined
	* @param {any} value 可以是任何类型
	**/
	_onFullfilled(value){}

	/**
	* 动态生成的,根据then,catch的回调函数生成,
	* 在构造函数中初始化, 初始化的值为undefined
	* @param {any} err 可以是任何类型
	**/
	_onRejected(err){}
	/**
	* @param {Function} func类型是一个函数,形如: (resolve, reject) => {}。resovle和reject也
	* 是函数,形如: (result) => {}
	**/
	constructor(func){}
	
  	/**
	* @param {Function} onResolve 是一个形如(result) => {}的函数
	* @param {Function} onReject 是一个形如(result) => {}的函数
	* @return 返回一个新的MyPromise对象
	**/
	then(onResovle, onReject){}
	
  	/**
	* @param {Function} onReject 是一个形如(result) => {}的函数
	* @return 返回一个新的MyPromise对象
	**/	
	catch(onReject){}
	
	/**
	* constructor 中func的两个参数中resolve对应的实参,但是需要先调用bind绑定this
	* @param {Any} value 可以是任何类型
	**/
	_resolve(value){}
	/**
	**	constructor 中func的两个参数中reject对应的实参,但是需要先调用bind绑定this
	**  @param {Any} err 可以是任何类型
	**/
	_reject(err){}
}

           

高阶函数

在实现之前,我们先看看高阶函数:

// 我们希望有这样一系列函数,f1在接受到参数执行完后把结果传递到f2, f2执行完后把结果传递到f3, ...
// 依次类推,直到fn执行完后,返回最终结果。我们可以这样来定义一个高阶函数(为了把问题简单化,这里我使用的是单参数函数):
const f = f1 => f2 => f3 => ... => fn => (params) => fn(...(f3(f2(f1(params)))))...)

// 于是乎我们这样来调用
f(f1)(f2)(f3)...(fn)(params)

           

在我看来Promise的then方法最终的结果跟上述高阶函数是一样的就是要构造出类似这样的调用形式:

在实现过程中,稍微有点不同的是,我们需要考虑返回值为MyPromise类型的时候,我们需要在使用then方法,传入回调函数。更容易理解一些的写法就是这样:

const f = (params) => {
	let rs
	rs = f1(params)
	if (rs instanceOf MyPromise){
		rs = rs.then(f2)
	} else {
		rs = f2(rs)
	}
	if (rs instanceOf MyPromise){
		rs = rs.then(f3)
	} else {
		rs = f3(rs)
	}
	...
	
	if (rs instanceOf MyPromise){
		rs = rs.then(fn)
	} else {
		rs = fn(rs)
	}
}
           

类比高阶函数的实现

根据上述设想,结合Promise的链式调用形式:

new Promise((f1) => {  // Promise 1
	f1(1)
}).then(f2)   // Promise 2
  .then(f3) // Promise 3
...
  .then(fn) // Promise n

           

每次执行完then之后,返回一个新的Promise。我们最终的目的就是要在链式调用完成后形成这样一个函数:

对于上面的例子,我们可以这样来设想,Promise的执行过程应该是这样:

前端实现一个异步的promise参考文档分析具体实现代码

需要注意的是,在Promise.then(onResolve: Function, onReject: Function)中:

  1. 若onResolve执行抛错(参数需要考虑Promise类型),则在下一个调用节点调用时执行结果由onReject作为参数接收并执行,反之, 由onResolve作为参数接收并执行。
  2. 若onReject 执行抛错(参数不需要考虑Promise类型),则在下一个调用节点调用时执行结果由onReject作为参数接收并执行,反之, 由onResolve作为参数接收并执行。
  3. 链式调用执行到最后的节点,若是处于rejcted状态,需要抛出错误。

具体实现代码

网上找到的实现方案中大部分都用了队列来存储回调函数,但是实际上每次链式调用后都返回了一个新的Promise,导致队列中始终都只有一个回调函数,所以我认为使用队列存储回调函数是不必要的。下面我们来看看具体实现:

// Promise有3中状态: pending, fullfilled, rejected。
const PENGDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

export default class MyPromise {
  /**
   * @param {Function} func类型是一个函数,形如: (resolve, reject) => {}。resovle和reject也
   * 是函数,形如: (result) => {}
   **/
  constructor(func) {
    try {
      this.value = undefined;
      this.status = PENGDING;
      this._onFullfilled = undefined;
      this._onRejected = undefined;
      
      // then,catch中的回调函是异步调用的,这里用setTimeout来模拟
      func(
        (value) => {
          setTimeout(() => this._resolve(value));
        },
        (err) => {
          setTimeout(() => this._reject(err));
        }
      );
    } catch (err) {
      this._reject(err);
    }
  }

  /**
   * constructor 中func的两个参数中resolve对应的实参,但是需要先调用bind绑定this
   * @param {Any} value 可以是任何类型
   **/
  _reject(err) {
    if (this.status !== PENGDING) return;
    this.value = err;
    this.status = REJECTED;
    if (this._onRejected) {
    	this._onRejected(err);
    } else {
    	throw err
    }
  }

  	/**
	*constructor 中func的两个参数中reject对应的实参,但是需要先调用bind绑定this
	* @param {Any} err 可以是任何类型
    **/
  _resolve(value) {
    if (this.status !== PENGDING) return;
    this.value = value;
    this.status = FULFILLED;
    this._onFullfilled && this._onFullfilled(value);
  }

  /**
	* @param {Function} onResolve 是一个形如(result) => {}的函数
	* @param {Function} onReject 是一个形如(result) => {}的函数
	* @return 返回一个新的MyPromise对象
	**/
  then(onResolve, onReject) {
    return new MyPromise((resolve, reject) => {
      this._onFullfilled = handleResovle(onResolve, onReject, resolve, reject);
      this._onRejected = handleReject(onReject, resolve, reject);
    });
  }

  /**
  * @param {Function} func 是一个形如(result) => {}的函数
  * @return 返回一个新的MyPromise对象
  **/
  catch(func) {
    return this.then(null, func);
  }
}

/**
* 我们可能这样写Promise:
* new Promise((resolve) => {resovle(1)})
* handleResovle的核心思想是把上述Promise对象中执行resolve函数后的结果抛给它返回的新的Promise对象,
* 作为这个新的Promise对象执行resolve函数时的参数。需要注意的是要处理返回值是Promise类型的情况,当然在这里是要
* 处理MyPromise的类型。
* @param {Function} onResolve
* @param {Function} onReject
* @param {Function} onResolveNext
* @param {Function} onRejectNext
**/
const handleResovle = (onResolve, onReject, onResolveNext, onRejectNext) => (value) => {
  if (value instanceof MyPromise) {
    value.then(onResolve, onReject).then(onResolveNext, onRejectNext);
    return;
  }
  try {
    let rs = onResolve(value);
    if (rs instanceof MyPromise) {
      rs.then(onResolveNext, onRejectNext);
    } else {
      onResolveNext(rs);
    }
  } catch (err) {
    onRejectNext(err);
  }
};

/**
* 我们可能这样写Promise:
* new Promise((reject) => {reject(1)})
* handleResovle的核心思想是把上述Promise对象中执行reject函数后的结果抛给它返回的新的Promise对象,
* 作为这个新的Promise对象执行reject函数时的参数。
* 这里和handleResovle不同,为了跟Promise保持一致,并没有考虑MyPromise类型的返回值
* @param {Function} onReject
* @param {Function} onResolveNext
* @param {Function} onRejectNext
**/
const handleReject = (onReject, onResolveNext, onRejectNext) => (err) => {
  let rs;
  if (onReject) {
    try {
      rs = onReject(err);
      onResolveNext(rs);
    } catch (e) {
      onRejectNext(e);
    }
  } else {
    onRejectNext(err);
  }
}
           

继续阅读