JS中的Promise的then方法做了啥?

楼主下去看了下Promise/A+, 这是Promise实现所要遵循的标准, 看了之后模拟了一份, 目前已经打包好放在promise-polyfill上。弄懂promise不容易啊。发出来大家学习啦~

export default class Promise {
    // internal method
    resolvePromise(y) {
        if (this.status === 'pending') {
            this.status = 'fulfilled'
            this.value = y

            if (Array.isArray(this.nextPromises)) {
                this.nextPromises.forEach((nextPromise, index) => {
                    const onFulfilled = this.onFulfilleds[index]
                    if (typeof onFulfilled === 'function') {
                        const y = onFulfilled(y)
                        Promise.Resolve(nextPromise, this.value)
                        this.onFulfilleds[index] = null
                    }
                })  
            }
        }
    }

    // internal method
    rejectPromise(r) {
        if (this.status === 'pending') {
            this.status = 'rejected'
            this.result = r
            if (Array.isArray(this.nextPromises)) {
                this.nextPromises.forEach((nextPromise, index) => {
                    const onRejected = this.onRejecteds[index]
                    if (typeof onRejected === 'function') {
                        const x = onRejected(r)
                        Promise.Resolve(nextPromise, this.result)
                        this.onRejecteds[index] = null
                    }
                })
            }
        }
    }

    // internal method
    static Resolve(nextPromise, x) {
        if (nextPromise === x) { throw new TypeError() }
        if (x instanceof Promise) { 
            x.then(nextPromise.resolvePromise, nextPromise.rejectPromise) 
        }  
        if (typeof x === 'object' || typeof x === 'function') {
            const then = x.then
            if (typeof then === 'function') {
                const resolvePromise = nextPromise.resolvePromise.bind(nextPromise)
                const rejectPromise = nextPromise.rejectPromise.bind(nextPromise)
                try {
                    then.call(x, resolvePromise, rejectPromise)
                } catch (e) {
                    nextPromise.rejectPromise(e)
                }
            } else {
                nextPromise.resolvePromise(x)
            }
        } else {
            nextPromise.resolvePromise(x)
        }  
    }

    constructor(executor) {
        this.status = 'pending'
        const resolvePromise = this.resolvePromise.bind(this)
        const rejectPromise  = this.rejectPromise.bind(this)
        executor(resolvePromise, rejectPromise)
    }

    then(onFulfilled, onRejected) {
        const nextPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                if (this.status === 'fulfilled') {
                    const x = onFulfilled(this.value)
                    Promise.Resolve(nextPromise, x)
                } else if (this.status === 'rejected') {
                    const x = onRejected(this.result)
                    Promise.Resolve(nextPromise, x)
                } else {
                    this.onFulfilleds = this.onFulfilleds || []
                    this.onRejecteds  = this.onRejecteds  || []
                    this.nextPromises = this.nextPromises || []

                    const length = this.nextPromises.length
                    (typeof onFulfilled === 'function') && (this.onFulfilleds[length] = onFulfilled)
                    (typeof onRejected === 'function')  && (this.onRejecteds[length]  = onRejected)
                    this.nextPromises.push(nextPromise)
                }
            }, 0)
        })

        return nextPromise
    }
}

--------------------------之前的东东

Promise.resolve(1).then(arg => {
    return new Promise(arg)
}).then(arg => console.log(arg))

这串代码没有任何输出结果,问了下gitter,说原因是new Promise(arg)是一个状态为Pending的promise对象, 会在这条链上卡主, 不会往下执行. 这让我感到有点困惑, 赶紧去翻了下MDN.MDN是这样描述then方法的.

the then method returns a Promise, you can easily chain then calls. Values returned from the onFulfilled or onRejected callback functions will be automatically wrapped in a resolved promise.

看了MDN的描述,百思不得其解. 对Promise的内部原理甚为困惑. 一个promise对象(状态是fulfilled)调用then的时候会生成一个promise对象, 这个promise对象是pending的, 会异步执行then方法的第一个回调方法, 然后Promise内部是如何包装then方法的返回值的呢?

楼主有一个假设, 用代码描述出来, 不知道是否正确....

function then(resolveHandler, rejectHandler) {
    const newPromise = new Promise((resolve, reject) => {
        setTimeout(() => {
            // 如果调用then方法的promise的status是rejected
            if(this.status === 'rejected') {
                rejectHandler(this.result)
                return
            }

            // 不是rejected 就是fulfilled
            const returnValue = resolveHandler()
            
             // 如果returnValue不是promise类型的话
            if (returnValue.___proto__ !== Promise) {
                wrapPromise = Promise.resolve(returnValue)  //包装返回值
                wrapPromise.then(resolve)
            } else {
                returnValue.then(resolve, reject)
            }
            
        }, 0)
    })

    return newPromise
}

楼主想了之后觉得then是这样的. 大神们给出新姿势~

阅读 22.7k
7 个回答

———————— 后更

再次一句话来说明:
Promise.prototype.then回调里面的返回值,是通过一个变量/私有API保存的,不是return出来的,想接收/包装返回值得在Promise状态完成了之后通知执行回调函数

我再简单的举例,一个简单的异步任务如何拿到值:

var foo = (callback) => {
    //通过回调函数拿值
    setTimeout(function () {
        callback(1);
    });
};

promise进行一下扁平化的封装

var Promise = function (callback) {
    if (!(this instanceof Promise)) {
        return new Promise(callback);
    }
    var $this = this;
    setTimeout(function () {
        callback(function (value) {
            //执行callback
            $this.callback(value);
        });
    });
}


Promise.prototype.then = function (resolveHandler) {
    this.callback = resolveHandler;
    return this;
};


Promise(function (resolve) {
    setTimeout(function () {
        resolve(1);
    }, 300)
}).then(function (value) {
    console.log(value);
});
//300ms后输出1

正常返回int,string类型都是比较好处理,而返回一个Promise该如何处理呢?—— 使用回调链啊

然后我把原回答的代码再次简化了一下,当然这个代码只能大概展现then对于返回值处理的原理,真正要完成Promise还有很多的细节处理

—————— 下面是原回答:

看了半天有点没太懂,在chrome dev跑了下代码发现你的代码报错了,导致了new Promise那里卡住了。

Promise.resolve(1).then(arg => {
    return new Promise(arg);//这里直接是new Promise(1),API不支持
}).then(arg => console.log(arg))

然后回答你后面的困惑,thenPromise返回值应该进行异步包装,并且仅依靠promise的公开API是无法实现then返回的包装的,需要一层链进行包装才可以。

简单的说就是:Promise.prototype.then中,正常的返回值,仅需要进行Promise一层包装即可,而Promise类型的返回值,则需要操作私有API进行回调链的包装,大概类似下面这样:


//为了例子简单点,只做一个resolveHandler
Promise.prototype.then = function(resolveHandler) {
    var promise = this;
    return new Promise((resolve, reject) => {
        var newPromise = this;
        setTimeout(function () {
            if (promise.status == 'pending') {//如果当前Promise的任务没有完成

                //如果已经有了回调函数onFulfilled,则再包装出回调链  => 这个回调函数要在Promise状态完成后执行
                if (promise.onFulfilled) {
                    //这里是重点,这里是重点,这里是重点,重要的事情说三遍
                    const oldOnFulfilled = promise.onFulfilled;
                    promise.onFulfilled = () => {
                        //进行链传递
                        var oldValue = oldOnFulfilled();
                        //如果上一个promise也返回了一个promise,则把当前的函数压入上一个promise链中
                        if (oldValue.___proto__ === Promise)
                            newPromise.onFulfilled = resolveHandler;
                        else
                            resolveHandler(oldValue);

                    };
                } else {
                    //没有回调函数,则压入回调函数
                    promise.onFulfilled = () => {
                        //传递返回值
                        resolveHandler(promise.result);
                    };
                }

            }
        });
    });

};

研究过很长一段时间的Promise,所以自己也实现过一个Promise-polyfill,可以参考下实现的细节。

个人网站:听说

代码写的有问题。
new Promise接受两个参数resolve, rejectthen接受两个参数resolved, rejected(都是函数)
resolved获得的是resolve传给它的值;同样,rejected获得的是也是reject的值。

楼主下去看了下Promise/A+, 这是Promise实现所要遵循的标准, 看了之后模拟了一份, 目前已经打包好放在promise-polyfill上。弄懂promise不容易啊。发出来大家学习啦~

export default class Promise {
    // internal method
    resolvePromise(y) {
        if (this.status === 'pending') {
            this.status = 'fulfilled'
            this.value = y

            if (Array.isArray(this.nextPromises)) {
                this.nextPromises.forEach((nextPromise, index) => {
                    const onFulfilled = this.onFulfilleds[index]
                    if (typeof onFulfilled === 'function') {
                        const y = onFulfilled(y)
                        Promise.Resolve(nextPromise, this.value)
                        this.onFulfilleds[index] = null
                    }
                })  
            }
        }
    }

    // internal method
    rejectPromise(r) {
        if (this.status === 'pending') {
            this.status = 'rejected'
            this.result = r
            if (Array.isArray(this.nextPromises)) {
                this.nextPromises.forEach((nextPromise, index) => {
                    const onRejected = this.onRejecteds[index]
                    if (typeof onRejected === 'function') {
                        const x = onRejected(r)
                        Promise.Resolve(nextPromise, this.result)
                        this.onRejecteds[index] = null
                    }
                })
            }
        }
    }

    // internal method
    static Resolve(nextPromise, x) {
        if (nextPromise === x) { throw new TypeError() }
        if (x instanceof Promise) { 
            x.then(nextPromise.resolvePromise, nextPromise.rejectPromise) 
        }  
        if (typeof x === 'object' || typeof x === 'function') {
            const then = x.then
            if (typeof then === 'function') {
                const resolvePromise = nextPromise.resolvePromise.bind(nextPromise)
                const rejectPromise = nextPromise.rejectPromise.bind(nextPromise)
                try {
                    then.call(x, resolvePromise, rejectPromise)
                } catch (e) {
                    nextPromise.rejectPromise(e)
                }
            } else {
                nextPromise.resolvePromise(x)
            }
        } else {
            nextPromise.resolvePromise(x)
        }  
    }

    constructor(executor) {
        this.status = 'pending'
        const resolvePromise = this.resolvePromise.bind(this)
        const rejectPromise  = this.rejectPromise.bind(this)
        executor(resolvePromise, rejectPromise)
    }

    then(onFulfilled, onRejected) {
        const nextPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                if (this.status === 'fulfilled') {
                    const x = onFulfilled(this.value)
                    Promise.Resolve(nextPromise, x)
                } else if (this.status === 'rejected') {
                    const x = onRejected(this.result)
                    Promise.Resolve(nextPromise, x)
                } else {
                    this.onFulfilleds = this.onFulfilleds || []
                    this.onRejecteds  = this.onRejecteds  || []
                    this.nextPromises = this.nextPromises || []

                    const length = this.nextPromises.length
                    (typeof onFulfilled === 'function') && (this.onFulfilleds[length] = onFulfilled)
                    (typeof onRejected === 'function')  && (this.onRejecteds[length]  = onRejected)
                    this.nextPromises.push(nextPromise)
                }
            }, 0)
        })

        return nextPromise
    }
}

你这实现好像有点问题啊

    let promise = new Promise(function(resolve, reject) {
        console.log('Promise');
        setTimeout(() => {
            reject('error');
        },2000)
    });
    promise.then(function() {
        console.log('resolved.');
    },function(error){
        console.log('reject')
    })

    console.log('Hi!');

输出依次是 prmise、Hi、resolved,很奇怪,为什么有 resolved

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏