JavaScript異步編程:Promise篇

前言

異步編程作爲JavaScript 中的核心內容,是必須要掌握的一個基礎。在日常開發過程中,會有很多異步的場景。比如定時器,網絡請求,事件監聽等等… 而異步編程中也有很多讓人詬病的點,比如 callback hell(回調地獄)。
這個系列是記錄下自己平常對異步知識的一些思考和記錄。之後還會有對generator函數以及async/await的文章。

正文

這一篇文章不是關注promise API的使用,而是根據promise/A+的規範來實現一個promise。由於涉及到具體實現,需要有一定的JavaScript基礎。

規範地址

中文版
英文版

具體實現

/**
	自定義Promise實現,遵循Promise/A+規範

*/
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';

function Promise(fn){
	var self = this;

	if (!(this instanceof Promise)){
		throw new Error('must use a new operator symbol!');
	}

	if (!fn) {
		throw new Error('a param is required!');
	}

	if (typeof fn !== 'function') {
		throw new Error('the param must be a function!');
	}

	this.status = PENDING;
	this.value = undefined;
	this.reason = undefined;


	//異步事件隊列
	this.resolveQuene = [];
	this.rejectQuene = [];

	// 觸發成功回調
	var resolveAction = function(param){

		if (self.status === PENDING) {
			self.status = FULFILLED;
			self.value = param;
			// 執行成功回調方法隊列
			for (var i = 0;i < self.resolveQuene.length;i++) {
				self.resolveQuene[i] && self.resolveQuene[i]();
			}
		}

	}

	// 觸發失敗回調
	var rejectAction = function(reason){

		if (self.status === PENDING) {
			self.status = REJECTED;
			self.reason = reason;

			// 執行失敗回調方法隊列
			for (var i = 0;i < self.rejectQuene.length;i++) {
				self.rejectQuene[i] && self.rejectQuene[i]();
			}
		}

	}

	try {
		fn.call(null,resolveAction,rejectAction);
	} catch(e){
		rejectAction(e);
	}
}



Promise.resolve = function(param){
	return new Promise(function(resolve){
		resolve(param);
	});
}

Promise.reject = function(reason){
	return new Promise(function(resolve,reject){
		reject(reason);
	});
}

Promise.prototype.catch = function(onRejected){
	return this.then(null,onRejected);
}

Promise.all = function(promiseArray){
	if (!Array.isArray(promiseArray)) {
		throw new Error('param must be a array');
	}
	var store = [];
	return new Promise(function(resolve,reject){
		for (var i = 0; i < promiseArray.length; i ++) {
			promiseArray[i].then(function(value){
				store.push(value);
				if (store.length === promiseArray.length) {
					resolve(store);
				}
			},function(reason){
				reject(reason);
			});
		}
	});
}

Promise.race = function(promiseArray){
	if (!Array.isArray(promiseArray)) {
		throw new Error('param must be a array');
	}
	return new Promise(function(resolve,reject){
		for (var i = 0; i < promiseArray.length; i ++) {
			promiseArray[i].then(function(value){
				resolve(store);
			},function(reason){
				reject(reason);
			});
		}
	});
}

Promise.assistPromise4resolve = function(promise,x,resolve,reject){
	if (x === promise) {
		throw new TypeError('chaning Promise error');
	} else if (x !== null && (typeof x === 'object' || typeof x === 'function')){
		let flag = false;
		try {
			var then = x.then;
			if (typeof then === 'function') {
				then.call(x,function(y){
					if (flag) return;
					flag = true;
					Promise.assistPromise4resolve(promise,y,resolve,reject);
				},function(reason){
					if (flag) return;
					flag = true;
					reject(reason);
				})
			} else {
				if (flag) return;
				flag = true;
				resolve(x);
			}
		} catch(e) {
			if (flag) return;
			flag = true;
			reject(e);
		}
		
	} else {
		resolve(x);
	}
}


Promise.prototype.then = function(onFulfilled,onRejected){
	// promise 規範 : 每一個then方法必須要返回一個新的promise以支持鏈式調用
	// 爲什麼不直接返回一個 this 呢?
	// 1.因爲promise的狀態只能改變一次
	
	// 指向上一個promise的實例
	var self = this;

	/*
		如果 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)
		如果 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,並返回拒因 e
		如果 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行並返回相同的值
		如果 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的據因
	*/
	var nextPromise =  new Promise(function(resolve,reject){
		if (self.status === FULFILLED) {
		// 爲了確保代碼的邏輯順序,規範要求then的回調要異步執行
			setTimeout(function(){
				try {	
					if (typeof onFulfilled === 'function') {
						var innerValue = onFulfilled(self.value);
						Promise.assistPromise4resolve(nextPromise,innerValue,resolve,reject);
					} else {
						resolve(self.value);
					}
				} catch (e){
					reject(e)
				}
			});
		}
		
		if (self.status === REJECTED) {
			// 爲了確保代碼的邏輯順序,規範要求then的回調要異步執行
			setTimeout(function(){
				try {
					if (typeof onRejected === 'function') {
						var innerReason = onRejected(self.reason);
						Promise.assistPromise4resolve(nextPromise,innerReason,resolve,reject);
					} else {
						reject(self.reason);
					}
					
				} catch (e) {
					reject(e);
				}
				
			});

		}

		// 假如實例化promise時執行的是異步代碼,此時狀態還是pending
		// 爲了確保代碼的邏輯順序,規範要求then的回調要異步執行

		if (self.status === PENDING) {
			self.resolveQuene.push(function(){
				setTimeout(function(){
					try {	
						if (typeof onFulfilled === 'function') {
							var innerValue = onFulfilled(self.value);
							Promise.assistPromise4resolve(nextPromise,innerValue,resolve,reject);
						} else {
							resolve(self.value);
						}
					} catch (e){
						reject(e)
					}
				})
			});

			self.rejectQuene.push(function(){
				setTimeout(function(){
					try {
						if (typeof onRejected === 'function') {
							var innerReason = onRejected(self.reason);
							Promise.assistPromise4resolve(nextPromise,innerReason,resolve,reject);
						} else {
							reject(self.reason);
						}
						
					} catch (e) {
						reject(e);
					}
				});
			});
		}
	});
	
	return nextPromise;

}



module.exports = Promise;

如何驗證這個promise的準確性呢,這裏有一個cli工具,定義了800多個測試用例,可以下載下來測試。

npm install promises-aplus-tests -D

注意爲了測試這個promise的實現,需要加上這麼一段代碼供這個工具調用

Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}

接下來運行一下這個測試工具。

 npx promises-aplus-tests ./promise.js 

可以看到下面的結果,是可以通過所有測試用例的。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章