Skip to content
On this page

介绍

手写 Promise

虽然大部分人开发的时候已经用不上考虑 IE 了,但是我相信还是有像我一样,要求兼容 IE 的需求

我也经常写 ES6,其中 Promise 用的也是最多的。所以在学习 Promises/A+ 标准后,特地使用 ES5 语法编写了一套 Promise,通过了 Promises/A+ 检查

实现

支持 Promise.all,Promise.resolve,Promise.defer,finally

其他的功能有兴趣的可以自己尝试补充

javascript
var PENDING = 'PENDING';
var RESOLVE = 'RESOLVE';
var REJECT = 'REJECT';

var isPromise = function (arg) {
	if ((typeof arg === 'object' && arg !== null) || typeof arg === 'function') {
		if (typeof arg.then === 'function') {
			return true;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

var resolvePromise = function (promise2, x, resolve, reject) {
	if (promise2 === x) {
		return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
	}

	if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
		var called;
		try {
			var then = x.then;
			if (typeof then === 'function') {
				then.call(
					x,
					function (y) {
						if (called) return;
						called = true;
						resolvePromise(promise2, y, resolve, reject);
					},
					function (r) {
						if (called) return;
						called = true;
						reject(r);
					}
				);
			} else {
				if (called) return;
				called = true;
				resolve(x);
			}
		} catch (error) {
			if (called) return;
			called = true;
			reject(error);
		}
	} else {
		resolve(x);
	}
};

function Promise(execute) {
	// 如果不是function,则报错
	if (typeof execute !== 'function') {
		throw Error('Promise resolver undefined is not a function');
	}

	var _self = this;
	this.status = PENDING;

	this.onFulfilledCbs = [];
	this.onRejectedCbs = [];

	this.resolve = function (value) {
		if (_self.status === PENDING) {
			_self.status = RESOLVE;
			_self.value = value;
			_self.onFulfilledCbs.forEach(function (item) {
				return item();
			});
			_self.onFulfilledCbs.length = 0;
			_self.onRejectedCbs.length = 0;
		}
	};

	this.reject = function (value) {
		if (_self.status === PENDING) {
			_self.status = REJECT;
			_self.value = value;
			_self.onRejectedCbs.forEach(function (item) {
				return item();
			});
			_self.onFulfilledCbs.length = 0;
			_self.onRejectedCbs.length = 0;
		}
	};

	try {
		execute(this.resolve, this.reject);
	} catch (error) {
		this.reject(error);
	}
}

Promise.prototype.then = function (onFulfilled, onRejected) {
	var _self = this;

	onFulfilled =
		typeof onFulfilled === 'function'
			? onFulfilled
			: function (data) {
					return data;
			  };
	onRejected =
		typeof onRejected === 'function'
			? onRejected
			: function (err) {
					throw err;
			  };

	var promise2 = new Promise(function (resolve, reject) {
		if (_self.status === RESOLVE) {
			setTimeout(function () {
				try {
					var x = onFulfilled(_self.value);
					resolvePromise(promise2, x, resolve, reject);
				} catch (error) {
					reject(error);
				}
			}, 0);
		}
		if (_self.status === REJECT) {
			setTimeout(function () {
				try {
					var x = onRejected(_self.value);
					resolvePromise(promise2, x, resolve, reject);
				} catch (error) {
					reject(error);
				}
			}, 0);
		}
		if (_self.status === PENDING) {
			_self.onFulfilledCbs.push(function () {
				setTimeout(function () {
					try {
						var x = onFulfilled(_self.value);
						resolvePromise(promise2, x, resolve, reject);
					} catch (error) {
						reject(error);
					}
				}, 0);
			});
			_self.onRejectedCbs.push(function () {
				setTimeout(function () {
					try {
						var x = onRejected(_self.value);
						resolvePromise(promise2, x, resolve, reject);
					} catch (error) {
						reject(error);
					}
				}, 0);
			});
		}
	});

	return promise2;
};

Promise.prototype.finally = function (cb) {
	return this.then(
		function (data) {
			return Promise.resolve(cb(data)).then(function () {
				return data;
			});
		},
		function (err) {
			return Promise.resolve(cb(data)).then(function () {
				throw err;
			});
		}
	);
};

Promise.resolve = function (arg) {
	if (isPromise(arg)) {
		var promise2 = new Promise(function (resolve, reject) {
			setTimeout(function () {
				try {
					resolvePromise(promise2, arg, resolve, reject);
				} catch (error) {
					reject(error);
				}
			}, 0);
		});
		return promise2;
	} else {
		return new Promise(function (resolve, reject) {
			resolve(arg);
		});
	}
};

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

Promise.all = function (values) {
	return new Promise(function (resolve, reject) {
		var arr = [];
		var index = 0;

		var pushIn = function (key, data) {
			arr[key] = data;
			index++;
			if (index === values.length) resolve(arr);
		};

		for (var i = 0; i < values.length; i++) {
			(function (i) {
				var item = values[i];
				Promise.resolve(item).then(function (data) {
					pushIn(i, data);
				});
			})(i);
		}
	});
};

Promise.author = 'LXH';