美文网首页es6
前端战五渣学JavaScript——Promise

前端战五渣学JavaScript——Promise

作者: 戈德斯文 | 来源:发表于2019-03-21 10:23 被阅读0次

    我是要成为海贼王的男人

    悟空已成神,鸣人已成影,待路飞成王之时,便是我青春结束时!

    悟空陪布玛找寻龙珠,一路拳打比克、斩弗利萨,生个儿子战沙鲁,最后净化布欧,只因承诺要保护地球。鸣人“有话直说,说到做到,这就是我的忍道”,一句会把佐助带回来的承诺,断臂践行。路飞要凑齐10个船员,成为海贼王,我们相信路飞一定会成王,因为我们相信他的承诺。

    我为什么说承诺呢,今天主题不是Promise吗,因为⬇️

    promise

    回调地狱 Callback Hell

    如果看这篇文章的你是有过项目经验的,应该都遭遇过这惨绝人寰的“回调地狱”。“回调地狱”并不是JS或者编程语言中的一种形式,只是大家把这种编程中遇到的现象、问题预定俗称的调侃成“回调地狱”。因为只要陷进去,就很难出来。并且回调地狱在代码层级上会越陷越深,逻辑看着会非常会乱,如下代码⬇️

    // 我们用setTimeout模拟线上发送请求等异步执行的函数
    setTimeout(() => {
        console.log(1);
        setTimeout(() => {
            console.log(2);
            setTimeout(() => {
                console.log(3);
            }, 1000)
        }, 1000) 
    }, 1000);
    

    这是三个回调函数嵌套,延迟一秒后输出1,再过一秒输出2,再过一秒输出3。当然现实项目中,每个函数里面处理的逻辑肯定不仅仅只是输入一个数字这么简单,当我们回调嵌套很多的时候,如果产品提出的一个需求我们需要更改执行顺序,这个时候我们会发现嵌套逻辑复杂到难以简单的更改顺序,严重的只能重新写这段的逻辑代码。并且回调函数让逻辑很不清晰。
    后来就有人提出了Promise概念,这个概念意在让异步代码变得非常干净和直观。

    Promise 这就是我的忍道

    这个概念并不是ES2015首创的,在ES2015标准发布之前,早已有Promise/APromise/A+等概念的出现,ES2015中的Promise标准便源自于Promise/A+Promise最大的目的在于可以让异步函数变得竟然有序,就如我们需要在浏览器中访问一个JSON座位返回格式的第三方API,在数据下载完成后进行JSON解码,通过Promise来包装异步流程可以使代码变得非常干净。———————摘自《实战ES2015》

    上面最重要的一句就是可以让异步函数变得竟然有序,可能有人会说awaitasync也可以让异步函数同步执行,但是await操作符本来就是用于等待一个Promise对象的。
    我们先来看一下Promise是怎么解决上面回调地狱这样的难题的⬇️

    // 封装一层函数
    function timeout() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000)
      })
    }
    // 按回调函数的逻辑执行
    timeout().then(() => {
        console.log(1);
        return timeout()
    }).then(() => {
        console.log(2);
        return timeout()
    }).then(() => {
        console.log(3);
    });
    

    我们按照回调函数的逻辑用Promise重新写了一遍,执行结果一样,我们可以看出来,相比回调函数的层级深入,使用Promise以后函数的层级明显减少了,逻辑清晰许多。


    下面我们来从头开始认识Promise

    Promise基础

    想要给一个函数赋予Promise的邓丽,就要先创建一个Promise对象,并将其作为函数值返回。Promise构造函数要求传入一个函数,并带有resovlereject参数。一个成功回调函数,一个失败成功回调函数。下面是Promise对象的三个状态:

    • pending: 初始状态,既不是成功,也不是失败状态。
    • fulfilled: 意味着操作成功完成。
    • rejected: 意味着操作失败。

    三个状态的转换关系是从pending -> fulfilled或者pending -> rejected,并且状态改变以后就不会再变了。pending -> fulfilled以后会去执行传入Promise对象的resovle函数,对应的,pending -> rejected以后会去执行传入Promise对象的reject函数。

    .then()

    resovle函数和reject函数是怎么传进去的呢,当然就是之前说的.then(),.then()可以接收两个参数,.then(onFulfilled[, onRejected])这是官方写法,其实就是.then(resovle, reject),第一个参数是成功回调,第二个参数就是失败回调。如下⬇️

    function timeout(isSuccess) {
      return new Promise((resolve, reject) => {
        if (isSuccess) {
          setTimeout(resolve, 1000)
        } else {
          reject()
        }
      })
    }
    
    timeout(true).then(() => {
      console.log('成功')
    }, () => {
      console.log('失败')
    });
    
    timeout(false).then(() => {
      console.log('成功')
    }, () => {
      console.log('失败')
    });
    

    我用if语句模拟一下成功和失败的场景,这就是.then()的用法。

    .catch()

    刚才说了.then()的第二个参数传进去的是一个失败回调的函数,但是Promise还有一个.catch()的方法,也是用来处理失败的,例子如下⬇️:

    function timeout() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000)
      })
    }
    
    timeout().then(() => {
      throw new Error('因为被凯多打败了,所以没当上海贼王')
    }).catch((err) => {
      console.log('失败原因:', err)
    });
    

    这时候也会输出错误信息。这时候你可能会问,那.then(resovle, reject)reject.catch(reject)有什么区别呢,下面是个人见解

    .then(resovle, reject)reject.catch(reject)有什么区别

    我个人认为,.then(resovle, reject)reject按就近原则,只对最近的这个异步函数进行错误处理,但是对以后的或者之前的异步函数不做处理,而.catch(reject)会捕获到全局所有链式上异步函数的错误。链式调用下面会讲到。总之就是.catch(reject)管的范围要大一些。

    链式调用

    Promise有一个对象链,并且这个对象链式呈流水线的模式进行作业,是因为在Promise对象对自身的onFulfilledonRejected相应器的处理中,会对其中返回的Promise对象进行处理。其中内部会将这个心的Promise对象加入到Promise对象链中,并将其暴露出来,使其继续接受新的Promise对象的加入。只有当Promise对象链中的上一个Promise对象进入成功或者失败阶段,下一个Promise对象菜户被激活,这就形成了流水线的作业模式。

    这就好比一开始使用Promise改造回调地狱函数时候的样子⬇️

    // 封装一层函数
    function timeout() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000)
      })
    }
    // 按回调函数的逻辑执行
    timeout().then(() => {
        console.log(1);
        return timeout()
    }).then(() => {
        console.log(2);
        return timeout()
    }).then(() => {
        console.log(3);
    });
    

    可以一层一层的传一下去,这也是厉害的地方。当链式调用中用.catch()捕获错误的时候是这样的⬇️

    function timeout() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000)
      })
    }
    
    timeout()
      .then(() => {
        console.log(1);
        return timeout(err)
      })
      .then(() => {
        throw new Error('发生错误了')
        return timeout(2)
      })
      .catch((err) => {
        console.log('123',err)
      })
      .then(() => {
        console.log(3);
      });
    

    这种情况,.catch()紧跟在抛出错误的一步函数后面,会抛出错误,然后继续往下执行,但是如果.catch()是在最后,结果就完全不一样了⬇️

    function timeout() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000)
      })
    }
    
    timeout()
      .then(() => {
        console.log(1);
        return timeout(err)
      })
      .then(() => {
        throw new Error('发生错误了')
        return timeout(2)
      })
      .then(() => {
        console.log(3);
      })
      .catch((err) => {
        console.log('123',err)
      });
    

    如果是这样,前面说了.catch()会捕获全局错误,但是,.catch()写在最后,抛出错误以后,函数会直接跳到.catch()然后继续往下执行,就像下面代码⬇️

    function timeout() {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 1000)
      })
    }
    
    timeout()
      .then(() => {
        console.log(1);
        return timeout()
      })
      .then(() => {
        console.log(11);
        throw new Error('发生错误了')
        return timeout()
      })
      .then(() => {
        return timeout(2)
      })
      .catch((err) => {
        console.log('2',err)
      })
      .then(() => {
        throw new Error('发生错误了2')
        console.log(3);
      })
      .catch((err) => {
        console.log('3',err)
      });
    

    上面这段代码就会直接跳过输出2的异步函数,直接走到第一个.catch(),然后再往下执行。

    Promise高级

    Promise.all()

    这个方法真的太实用了,比如你进入首页,需要同时请求各种分类,用户信息等等信息,咱们可能需要在所有的请求都回来以后再展示页面,因为我们不能确定每个请求都要多久才能请求回来,所以这个问题一度很难解决。现在有了Promise.all()这个方法,真的太方便了,下面就是例子⬇️

    // Promise.all()需要传入的就是一个数组,每一项就是每一个异步函数
    function timeout(delay) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, delay * 1000)
      })
    }
    
    Promise.all([
      timeout(1),
      timeout(3),
      timeout(5),
    ]).then(() => {
      console.log('都请求完毕了!')
    });
    

    上面代码会在最大延迟的5秒后然后在执行.then()的方法,当然还有一个差不多的函数,往下看

    Promise.race()

    Promise.race()会监听所有的Promise对象,在等待其中的第一个进入完成状态的Promise对象。一旦有第一个Promise对象进入了完成状态,该方法返回的Promise对象便会根据这第一个完成的Promise对象的状态而改变,如下⬇️

    function timeout(delay) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, delay * 1000)
      })
    }
    
    Promise.race([
      timeout(1),
      timeout(3),
      timeout(5),
    ]).then(() => {
      console.log('有一个请求已经结束!')
    });
    

    上面代码在执行1秒后就会执行.then()的方法,然后剩下的两个请求继续等待返回。
    反正我也没遇到过什么使用场景,知道有这个方法就行了

    只管把目标定在高峰,人家要笑就让他去笑!

    写到后面有点太官方的感觉,但是又觉得很不好解释,只能堆例子来解释了,跟大佬的差距还是有一定的差距,这只是基于我现在的水平到目前为止对Promise的理解。

    一句承诺,就要努力去兑现。自己选择的路,跪着也要走完。


    我是前端战五渣,一个前端界的小学生。

    相关文章

      网友评论

        本文标题:前端战五渣学JavaScript——Promise

        本文链接:https://www.haomeiwen.com/subject/ifafvqtx.html