资讯专栏INFORMATION COLUMN

Promise 的then 里发生了什么

Joyven / 2467人阅读

摘要:大家都知道怎么用,但是对于内部的原理很多人都不是很清楚来看一个面试题的是怎么实现的首先来分析一下是属于实例上的方法参数有个,分别为,并且都是可选的可以实现链式调用执行要执行或者方法参数,分别有自己的参数,分别是的参数跟的参数只能使用前一个的

Promise 大家都知道怎么用, 但是对于内部的原理很多人都不是很清楚

来看一个面试题: Promise的then 是怎么实现的

首先来分析一下then

then是属于实例上的方法

参数有2个,分别为onFulfilled, onRejected,并且都是可选的

可以实现链式调用

then执行要执行Promise onFulfilled 或者 onRejected 方法

参数onFulfilled,onRejected 分别有自己的参数, 分别是resolve的参数跟reject的参数

then只能使用前一个then的返回值

then返回值不能是同一个promise

来一个一个看吧

then是属于实例上的方法

Promise.prototype.then = function(){}

参数有2个,分别为onFulfilled, onRejected,并且都是可选的

Promise.prototype.then = function(onFulfilled,onRejected){}

可以实现链式调用

Promise.prototype.then = function(onFulfilled,onRejected){
    return new Promise(function(resolve,reject){
        // 代码省略
    })
}

要实现promise链式 返回的必须是一个Promise 再由于status 改变状态了不能再变 所以需要第二个.... 第N个promise 调用新的resolve

then执行要执行Promise onFulfilled 或者 onRejected 方法

Promise.prototype.then = function(onFulfilled,onRejected){
    var self = this; // 保存当前上下文
    return new Promise(function(resolve,reject){
        if(self.status === "resolved"){
             onFulfilled(self.res)
        }
        if(self.status === "rejected"){
            onRejected(self.err)
        }
    })
}

Promise.resolve(res) 、、 同步代码的执行情况下 上述逻辑成立

参数onFulfilled,onRejected 分别有自己的参数, 分别是resolve的参数跟reject的参数

res 跟err 来源如下

function Promise(executor){
    let self = this
    this.status = "pending"  // Promise 的状态值
    this.res = undefined   // 存成功之后的值
    this.err = undefined   // 存失败之后的值
    executor(resolve,reject)
    // 省略代码
}

executor 有2个参数 分别为resolve,reject
当调用

new Promise((resolve,reject)=>{
    setTimeout(()=>{
        if(true){
            resolve("res")
        }else{
            reject("err")
        }
       
    },1000)
})

executor 会执行 并且把成功的值或者失败的值抛出来,resolve跟reject也是2个函数 定义在Promise内部

function Promise(executor){
     // ...代码省略
    function resolve(res){
        self.res = res 
    }
    function reject(err){
        self.err = err 
    }
    executor(resolve,reject)
    
}

resolve 跟reject 还需要改变 Promise的状态值 并且一旦发生改变之后不能再次改变

function Promise(executor){
    // 代码省略
    function resolve(res){
        if(self.status === "pending"){
            self.status = "resolved"
            self.res = res 
        }
    }
    function reject(err){
        if(self.status === "pending"){
            self.status = "rejected"
            self.err = err 
        }
    }
    executor(resolve,reject)
}

我们在executor中操作的往往是异步代码, 这个之后直接then, status的状态值并未发生改变, 所以不会执行onFulfilled跟onRejected,
这个时候我们需要订阅

function Promise(executor){
    // 代码省略
    this.onFulfilledCallback = []  // 存成功之后的回调
    this.onrejectedCallback = []  // 存失败之后的回调
    function resolve(res){
        if(self.status === "pending"){
            self.status = "resolved"
            self.res = res 
        }
    }
    function reject(err){
        if(self.status === "pending"){
            self.status = "rejected"
            self.err = err 
        }
    }
    executor(resolve,reject)
}

new Promise(executor).then((onFulfilled,onrejected)=>{
    var self = this; // 保存当前上下文   **注意: 第二次调用then this是指向new Promise的**
    return new Promise(function(resolve,reject){
        // ...代码省略
        if(self.status === "pending"){
            self.onFulfilledCallback.push(function(){   // 把成功之后需要做的事存起来  有多少个then就有多少个函数
                onFulfilled(self.res)   // 注意 这里的self.res 会随着then的调用发生改变  因为每次then都new 了一个Promise
            })
            self.onrejectedCallback.push(function(){   // 把失败之后的事存起来 有多少个then就有多少个函数
                onFulfilled(self.err)
            })
        }
    })
})

当resolve的时候 或者reject的时候
一一拿出来执行

function resolve(res){
    // 省略代码
    self.res = res
    onFulfilledCallback.forEach(fn=>{
        fn()
    })
}

实现链式调用我们需要对then函数执行的返回值做一个操作!!!

需求:

then的参数onFulfilled函数 需要接收到上一个onfulfilled的执行结果

Promise.prototype.then = function(onFulfilled,onRejected){
    return new Promise((resolve,reject)=>{
        // ...代码省略
        if(self.status === "resolved"){
            let x = onFulfilled(self.res)   // 拿到onFulfilled的执行结果  注意:这里执行Promise.resolve() 同步代码
            // 然后把x传递给下一个then
            resolve(x)
        }
    })
}

如果resolve是异步的话

Promise.prototype.then = function(onFulfilled,onRejected){
    return new Promise((resolve,reject)=>{
        // ...代码省略
        if(self.status === "resolved"){
            let x = onFulfilled(self.res)   // 拿到onFulfilled的执行结果  注意:这里执行的是Promise.resolve() 同步代码
            // 然后把x传递给下一个then
            resolve(x)
        }
        if(self.status === "pending"){
            self.onFulfilledCallback.push(function(){
                let x = onFulfilled(self.res)  // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled}  
                resolve(x)
            })
        }
    })
}

//  同时为了拿到 fn(){let x = onFulfilled ...} 得值 传递给下一个onFulfilled,onFulfilledCallback需要改写 onRejectedCallback同理

onFulfilledCallback.forEach(fn=>{
    return fn()
})
onRejectedCallback.forEach(fn=>{
    return fn()
})

-

onFullfillled 的返回值可能是个promise 也可能是个普通值

考虑如下情况:

1. 返回值是个promise

Promsie.resolve(11).then(res=>{

    return new Promise(executor) 
})

2. 返回值是个普通值

Promsie.resolve(11).then(res=>{

    return 1
})

最关键的来了
我们需要对onFullfillled 的返回值进行判断

修改then函数

// ...代码省略
if(self.status === "resolved"){
        let x = onFulfilled(self.res)   // 拿到onFulfilled的执行结果  注意:这里执行的是Promise.resolve() 同步代码
        // 然后把x传递给下一个then
        promiseResolve(x,resolve,reject)
        
    }
function promiseResolve(x,resolve,reject){
    if((typeof x != null && typeof x == "object") || typeof x == "function"){  // 确定是个引用类型
        if (x instanceof Promise){  // 确定是个Promise 实例
            let then = x.then
            then.call(x,res=>{    //  保证x调用res=>{}, res=>{}是then的onFulfilled方法  一直判断直到onFulfilled 返回的是个普通值 然后resolve出去
                promiseResolve(res,resolve,reject)
            },err=>{
                reject(err)
            })
        }
    }else{
        resolve(x)
    }
}

then返回值不能是同一个promise

考虑这样极端问题:

let p = new Promise(resolve=>{
    resolve(3333)
})

let p2 = p.then(resolve=>{
    return p2
})

这样会出现什么问题呢, onFulfilled的返回结果如果是一个promise 上面的递归调用已经说明了 我们要不断的去递归最终的onFulfilled结果 然后再改变p2的status 这样变成了p2去等待p2的执行结果 函数死掉了

所以onFulfilled的返回结果需要跟then的返回结果去比较 修改函数

function promiseResolve(x,resolve,reject,promise){
    if(x === promise ){
        return new Error("引用错误!")
    }
    if((typeof x != null && typeof x == "object") || typeof x == "function"){  // 确定是个引用类型
        if (x instanceof Promise){  // 确定是个Promise 实例
            let then = x.then
            then.call(x,res=>{    //  保证x调用res=>{}, res=>{}是then的onFulfilled方法  一直判断直到onFulfilled 返回的是个普通值 然后resolve出去
                promiseResolve(res,resolve,reject)
            },err=>{
                reject(err)
            })
        }
    }else{
        resolve(x)
    }
}
Promise.prototype.then = function(onFulfilled,onRejected){

    // ...代码省略
    let promise2 = new Promise((resolve,reject)=>{
        if(self.status === "pending"){
            self.onFulfilledCallback.push(function(){
                let x = onFulfilled(self.res)  // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled}  
                promiseResolve(x,resolve,reject,promise2)
            })
        }
    })
    return promise2
}

这段代码还是有错误, 由于javascript主线程是单线程的, 所以在then里的promiseResolve是拿不到promise2的 所以我们需要开启异步 使用定时器或者nextTick 这里我们用定时器

Promise.prototype.then = function(onFulfilled,onRejected){

    // ...代码省略
    let promise2 = new Promise((resolve,reject)=>{
        if(self.status === "pending"){
            self.onFulfilledCallback.push(function(){
                setTimeout(()=>{
                    let x = onFulfilled(self.res)  // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled}  
                    promiseResolve(x,resolve,reject,promise2)
                },0)
            })
        }
    })
    return promise2
}

此时 整个then差不多完成了

需要补充的点

onFulfilled,onRejected 有可能是undefined 这里未做判断

在new Promise 时 executor函数内部有可能报错 这里未使用try catch捕获

这里对于onRejected的错误未抛出

总结

then每次创建一个新的promise对象 对于同步的resolve,reject直接调用onFulfilled或者onRejected ,对于异步的resolve,reject使用
订阅发布模式,把每个resolve,reject 暂存起来 等达到条件时候一一执行, 并且拿到返回结果去跟内部的promise比较,并且判断如果是一个promise的话,不断解析onFulfilled 的返回结果 直至resolve出去

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102006.html

相关文章

  • ES6 Promise - Promise生命周期和创建

    摘要:通过构造函数,可以创建一个。函数本身是一个异步行为,其方法的第三个参数为一个回调函数,用来接收文件读取的结果失败时候的和成功时候的。第一个函数的参数对应的参数,第二个回调函数对应的参数。 一:Promise的概念 Promise的中文意思是‘承诺’,什么叫承诺?承诺就是现在没有发生,在将来的某个时刻一定会发生的事情。放在编程语言的环境下,Promise就是异步事件的结果的占位符。我们不...

    Hydrogen 评论0 收藏0
  • 原创 | JS异步工具之--Promise

    摘要:作者珂珂沪江前端开发工程师本文为原创文章,有不当之处欢迎指出。只对未来发生的事情做出两种基本情况的应对成功和失败。在异步转同步这条道路上,只是一个出彩的点,他还尚有一些缺陷和不足,并不是我们最终的解决方案。 作者:珂珂 (沪江前端开发工程师)本文为原创文章,有不当之处欢迎指出。转载请标明出处。 一个新事物的产生必然是有其历史原因的。为了更好的以同步的方式写异步的代码,人们在JS上操碎了...

    alanoddsoff 评论0 收藏0
  • 马蹄疾 | 详解 JavaScript 异步机制及发展历程(万字长文)

    摘要:本文从入手,系统的回顾的异步机制及发展历程。需要提醒的是,文本没有讨论的异步机制。这就是之前提到的事件触发线程。其实无论是请求还是定时器还是事件,我们都可以统称它们为事件。第二阶段,引擎线程专注于处理事件。将外元素的事件回调放入调用栈。 functionshowImg(url){ varframeid=frameimg+Math.random(); window.img=window....

    shaonbean 评论0 收藏0
  • 写一个符合promiseA+规范promise实现

    摘要:如何写一个符合规范的实现前言是异步编程的一种解决方案从语法上讲,是一个对象,从它可以获取异步操作的消息从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。 如何写一个符合promiseA+规范的promise实现 前言 Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。pr...

    hatlonely 评论0 收藏0
  • Promise初级与进阶---都在这

    摘要:处理和前一个回调函数运行时发生的错误发生错误对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止。 0 前言 我一直以为我对Promise比较了解,相关的方法已经非常熟悉了,直到我看到这篇文章,里面提出了这样一个问题:Q: 假定 doSomething() 和 doSomethingElse() 均返回 promises,下面的四种 promises 的区别是什么 /...

    Ocean 评论0 收藏0

发表评论

0条评论

Joyven

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<