# 符合Promise A+规范的实现

// Promise的三种状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

/**
* 用于处理返回新Promise情况的递归,一直深入调用。
* 直到返回不带then函数类型属性的value,传递给startPromise
*/
const resolvePromise = (startPromise, x, resolve, reject) => {
    if (startPromise === x) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
    }

    let called;
    if ((typeof x === 'object' && x != null) || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(startPromise, y, resolve, reject);
                }, 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);
    }
};

class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];

        let resolve = (value) => {
            if (value instanceof Promise) {
                return value.then(resolve, reject);
            }

            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }

        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }

        try {
            executor(resolve, reject);
        } catch(err) {
            reject(err);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};

        let chainPromise = new Promise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(chainPromise, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }

            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(chainPromise, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }

            if (this.status === PENDING) {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(chainPromise, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(chainPromise, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
            }
        });

        return chainPromise;
    }

    static resolve(value) {
        return new Promise((resolve) => {
            resolve(value);
        });
    }

    static reject(reason) {
        return new Promise((_resolve, reject) => {
            reject(reason);
        });
    }

    catch(errCallback) {
        return this.then(null, errCallback);
    }

    finally(callback) {
        return this.then((value) => {
            return Promise.resolve(callback()).then(() => value);
        }, (reason) => {
            return Promise.reject(callback()).then(() => {throw reason});
        });
    }

    static all(values) {
        return new Promise((resolve, reject) => {
            let resultArr = [];
            let orderIndex = 0;

            const processResultByKey = (value, index) => {
                resultArr[index] = value;
                if (++orderIndex === values.length) {
                    resolve(resultArr);
                }
            };

            for (let i = 0; i < values.length; i++) {
                let value = values[i];
                if (value && typeof value.then === 'function') {
                    value.then((value) => {
                        processResultByKey(value, i);
                    }, reject);
                } else {
                    processResultByKey(value, i);
                }
            }
        });
    }

    static race(values) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < values.length; i++) {
                let value = values[i];

                if (value && typeof value.then === 'function') {
                    value.then(resolve, reject);
                } else {
                    resolve(value);
                }
            }
        });
    }
}

// 添加该方法可使用promises-aplus-tests模块测试Promise方法是否符合Promise A+规范
Promise.defer = Promise.deferred = function () {
    let dfd = {};

    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    });

    return dfd;
}

module.exports = Promise;