基于Promise/A规范实现Promise,采用ES5语法

前言

Promise是异步操作中最常用的一种模式,这篇文章我们探讨一下如何基于Promise/A规范实现一个Promise,完整实现采用ES5语法,运行环境为Node.js,使用process.nextTick实现推迟then指定的回调函数的执行。

简化后的Promise/A规范

查阅了一下Promise/A规范,总结出来了一下几个要求,Promise的功能基于以下几个要求来实现

  1. 使用new Promise创建一个对象,参数为一个方法,传入的方法有两个参数
  2. Promise.then方法为指定的Promise回调之后调用的方法,这个参数方法可以有一个参数,也可以有两个参数
  3. Promise.catch方法指定失败回调
  4. Promise.then和Promise.catch的返回值都是一个Promise,如果在回调函数中指定了返回的Promise,则返回这个promise,否则返回一个新的状态为 resolved的promise
  5. Promise有三种状态,分别是penging、resolved、rejected,如果状态改变成resolved或者rejected则无法再发生变化
  6. 使用Promise.then指定的回调函数,回调函数会在当前的同步代码执行完成后执行,如果Promise的状态已经是resolved,则回调也会推迟到下一次微事件循环时执行
  7. 同一个promise对象可以指定多个回调函数,这些回调函数的执行顺序按照声明的时候的顺序来执行

开始实现

根据Promise的使用经验,我们很容易的给出如下的代码结构:

function MyPromise(fn){
	// 私有方法
	function resolve(value){}
	function reject(value){}

	// 公有方法
	this.then = function(fn){}
	this.catch = function(fn){}
	this.finally = function(fn){}
	
	fn.call(global, resolve, reject); // 调用fn方法,并将resolve和reject作为参数传进去
}

然后MyPromise有三种状态,和一个值,添加这两个私有变量。然后同一个promise可以添加多个then、catch回调,这是我们需要用一个回调函数队列来实现。
改变后的代码结构如下:

function MyPromise(fn){
	// 私有属性
	var __status = 'pending';
	var __data;
	var resolveCallbacks = new Array();
	var rejectCallbacks = new Array();
	var finallyCallback;
	// 私有方法
	function resolve(value){}
	function reject(value){}

	// 公有方法
	this.then = function(fn){}
	this.catch = function(fn){}
	this.finally = function(fn){}
	
	fn.call(global, resolve, reject); // 调用fn方法,并将resolve和reject作为参数传进去
}

接下来就可以来实现我们的逻辑了,整个的过程思路是,在调用resolve方法之后,MyPromise的状态变成fulfilled,然后异步执行resolveCallbacks中注册的回调函数。调用reject方法也执行类型的操作。然后多次调用resolve或者reject是无效的。

在then方法中我们可以判断当前Promise的状态,然后执行不同的操作

代码如下:

function MyPromise(fn){
	// 私有属性
	var __status = 'pending';
	var __data;
	var resolveCallbacks = new Array();
	var rejectCallbacks = new Array();
	var finallyCallback;
	// 私有方法
	function resolve(value){
		if(___status !== 'pending') return;
		__status = 'resolved';
		__data = value;
		exec(resolveCallbacks);
	}
	function reject(value){
		if(___status !== 'pending') return;
		__status = 'rejected';
		__data = value;
		exec(rejectCallbacks);
	}
	
	// 这个方法在后面解释
	function exec(callbacks){
		while(callbacks.length > 0){
			var head = callbacks.shift();
			(function _temp(head){ // 利用匿名立即执行函数解决异步操作中的引用问题
				process.nextTick(function(){
					var result = head.f.call(global, __data);
					if(result instanceof MyPromise) result.then(function(d){
						head.r.call(global, d);
					})
					else head.r.call(global,result);
				})
			})(head)
		}
		if(finallyCallback) finallyCallback.call(global);
	}
	// 公有方法
	this.then = function(fn){
		var tempResolve, tempReject;
		var retPromise = new MyPromise(function(resolve, reject){
			tempResolve = resolve; // 拿到新的MyPromise对象的resolve方法引用
			tempReject = reject;
		})
		if(__status == 'resolved') {
			process.nextTick(function(){fn.call(global,__data);})
		}else if(__status == 'pending')
			resolveCallbacks.push({f:fn, r: tempResolve});
		if(arguments.length == 2){
			if(__status == 'rejected') {
				process.nextTick(function(){fn.call(global,__data);})
			}else if(__status == 'pending')
				rejectCallbacks.push({f:fn, r: tempReject});
		}
		return retPromise;
	}
	this.catch = function(fn){
		var tempReject;
		var retPromise = new MyPromise(function(resolve, reject){
			tempReject = reject;
		})
		if(__status == 'rejected') {
			process.nextTick(function(){fn.call(global,__data);})
		}else if(__status == 'pending')
			rejectCallbacks.push({f:fn, r: tempReject});
		
		return retPromise;
	}
	this.finally = function(fn){
		if(__status == 'pending') process.nextTick(function(){fn.call(global)})
		else finallyCallback = fn;
	}
	
	fn.call(global, resolve, reject); // 调用fn方法,并将resolve和reject作为参数传进去
}

关键实现思路

给出以下几个关键问题的解决方案:

  1. then回调函数是延迟执行的,那么我们怎么知道回调函数返回的是不是MyPromise呢?
    解决方案
    call函数的返回值是回调函数调用后的返回值。
  2. 链式then调用的问题怎么解决呢?
    解决方案
    在then函数中拿到新返回的Promise对象的resolve/reject函数的引用,因为两个函数可以改变MyPromise对象的状态,然后将这个函数和回调函数一起入回调队列,这样就可以实现根据then参数返回值决定then返回的MyPromise的状态了,几处核心代码:
    // 1.
    var tempResolve, tempReject;
    	var retPromise = new MyPromise(function(resolve, reject){
    		tempResolve = resolve; // 拿到新的MyPromise对象的resolve方法引用
    		tempReject = reject;
    	})
    
    // 2.
    rejectCallbacks.push({f:fn, r: tempReject});
    //3.
    var result = head.f.call(global, __data);
    if(result instanceof MyPromise) result.then(function(d){
    	head.r.call(global, d);
    })
    else head.r.call(global,result);
    
    

MyPromise测试

测试代码如下:

var promise = new MyPromise(function(resolve, reject) {
  setTimeout(() => {
    resolve("data");
    console.log("下一次宏任务中的事务");
  }, 400);
});

promise.then(function(data) {
  console.log(data + 1);
});
promise.then(data => {
  console.log(data + 2);
});
promise.then(data => {
  console.log(data + 3);
});
promise
  .then(data => {
    console.log(data + 4);
    return new MyPromise(function(resolve, reject) {
      setTimeout(() => resolve(data + "这是下一个promise的操作"), 1000);
    });
  })
  .then(res => console.log(res));

promise.then(function(data) {
  console.log(data + 5);
});
console.log("同步代码");

输出的结果如下

同步代码
下一次宏任务中的事务
data1
data2
data3
data4
data5
data这是下一个promise的操作

猜你喜欢

转载自blog.csdn.net/weixin_37994110/article/details/88765777
今日推荐