一篇博客了解Promise、Generator及async

背景

我们在实际的开发当中,会遇到很多这样的场景,就是我们不能立即知道接下来该如何去执行一段代码,就比如我们的ajax请求,因为根据很多外界的因素,我们获得服务器返回的数据的时间是不确定的,所以我们必须要等到结果出来之后,才知道接下来该做什么。

回调地狱

我们可以用原生的Ajax请求来模拟一个场景:

    <script>
        var url = 'https://www.abc/do';
        
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.send();
        
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                console.log(xhr.response);
            }
        }
    </script>

这是原生的ajax实现,我们需要用到onreadystatechange事件来确定我们的代码何时才能拿到我们的数据,拿到数据之后,才可以处理这些数据。光这样看起来,其实毫无毛病,但是如果这个时候我们又有一个ajax请求,但是其中的某个参数需要等到第一次的ajax结束后才能拿到,我们不得不这样去做:

    <script>
        var url = 'https://www.abc/do';
        
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.send();
        
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
               var url2 = "https://www.abc/happy?user=" + xhr.response.user;
               var xhr2 = new XMLHttpRequest();
               xhr2.open('GET', url);
               xhr2.send();
               xhr2.onreadystatechange = function() {
                   if(xhr2.readyState == 4 && xhr2.status == 200) {
                       console.log(xhr2.responseText);
                   }
               }
            }
        }
    </script>

当出现第3次ajax请求依赖于上一次请求的话,我们的代码就嵌套非常深了,变得非常恶心,这样的情况就被称之为回调地狱。于是我们需要一种手段来解决这种情况。

Promise

Promise就是用于解决回调地狱问题的,当然了除了这个,promise将数据的请求数据的处理明确的分开来了。然而我们原生的ajax对于这一块是很混乱的,当数据比较复杂的时候,就很难去维护了。Promise准确来说是一个构造函数,我们可以通过它构造出Promise对象。

Promise对象的3种状态
状态描述
pending等待/进行中,还未得到结果
resolved完成,得到了想要的结果,可以继续往下执行
rejected完成,得到了错误结果,拒绝执行

这三种状态只能是由pending改变成resolved或者rejected,是不可逆的。

如何使用Promise

我们可以用Promise构造函数构造一个Promise对象,在构造函数中,第一个参数传入一个函数,这个函数是用于处理Promise状态的变化的,看具体代码:

    <script>
        new Promise(function(resolve, reject){
            if(true) { resolve() }
            if(false) { reject() }
        })
    </script>

resolvereject都是函数,作用就是将pending状态修改为 resolved或者rejected。
promise对象有一个then方法,可以对不同状态分别执行,then方法接受两个参数,这俩参数都是函数,第一个函数是接受到resolved状态对应执行的函数,第二个则是接受到rejected状态对应执行的函数。

    <script>
        function isNumber(num){
            return new Promise((resolve, reject) => {
                if(typeof num == 'number') { resolve() }
                else { reject() }
            }).then(() => {
                console.log("是数字类型的");
            }, () => {
                console.log("不是数字类型的");
            });
        }

        isNumber(1);
        isNumber("Lan");
    </script>
Output:
是数字类型的
不是数字类型的

then方法执行结束后,依然会返回一个Promise对象,因此我们可以链式调用then方法:

    <script>
        function isNumber(num){
            return new Promise((resolve, reject) => {
                if(typeof num == 'number') { resolve() }
                else { reject() }
            }).then(() => {
                console.log("是数字类型的");
            }).then(null, () => {
                console.log("不是数字类型的");
            });
        }

        isNumber(1);
        isNumber("Lan");
    </script>

链式的去调用then方法也是解决回调地狱的主要方法。.then(null, function(){})也可以写成catch(function(){})

Promise数据传递

只需要把想要传递的数据座位参数放入到resolve()和reject()函数中即可,体会一波:

    <script>
        var succTip = "is Number";
        var errTip = "not Number";
        function isNumber(num){
            return new Promise((resolve, reject) => {
                if(typeof num == 'number') { resolve(succTip) }
                else { reject(errTip) }
            });
        }
        isNumber(1).then(tip => {
            console.log(tip);
        });
        isNumber("Lan").then(tip => {
            console.log(tip);
        });
    </script>
Output:
is Number
Uncaught (in promise) not Number
Promise封装ajax

我们可以先封装一个get方法:

    <script>
        var url = "http://www.abc.cn/test";
        function promsieAjax(url){
            return new Promise((resolve, reject) => {
                var xhr = new XMLHttpRequest();
                xhr.open("GET", url);
                xhr.send();
                xhr.onreadystatechange = function() {
                    if(xhr.readyState == 4) {
                        if(xhr.status == 200) {
                            var response = JSON.parse(xhr.responseText);
                            resolve(response);
                        }else {
                            reject(new Error(xhr.statusText));
                        }
                    }
                }
            })
        }

        promsieAjax(url).then(data => {
            console.log(data);
        }).catch(error => {
            console.log(error);
        });
    </script>

总之就是在正确的地方调用resolve,在错误的地方调用reject,并且把数据传递出来即可。

Promise.all

Promise.all接受一个由Promise对象组成的数组,然后返回一个新的Promise对象,必须当这个数组中所有的Promise对象的状态都是resolved的时候,新的Promise对象的状态才是resolved。例子:

    <script>
        var p1 = new Promise((resolve, reject) => {
            resolve("success 1");
        });

        var p2 = new Promise((resolve, reject) => {
            resolve("success 2");
        });

        var p = Promise.all([p1, p2]);
        p.then((data) => {
            console.log(data);
        }).catch(error => {
            console.log("error");
        })
    </script>
Output: ["success 1", "success 2"]
Promise.race

Promise.race 的用法和all的用法大致相同,同样的,Promise.race也是接受一个Promise的对象数组,不过新的Promise对象状态取决于数组中第一个Promise对象所变化的状态。直接看例子吧:

    <script>
        var p1 = new Promise((resolve, reject) => {
           setTimeout(() => {
               resolve("success 1");
           },1000);
        });

        var p2 = new Promise((resolve, reject) => {
            setTimeout(() => {
               resolve("success 2");
           },500);
        });

        var p = Promise.race([p1, p2]);
        p.then((data) => {
            console.log(data);
        }).catch(error => {
            console.log("error");
        })
    </script>
Output:success 2

Async/await

async是属于ES7规范里面的,现在已经广泛使用了,他其实是Generator函数的语法糖,是目前解决异步操作的最佳方案。

Async function

我们可以把async关键字放在一个函数的申明前面,像这样:

    <script>
        async function fn() {
            return 1;
        }
    </script>

async的作用就在于,指定的函数总是会返回一个Promise对象,就算本身我们返回的不是Promise对象,比如我们上面手动返回的1,它也会帮我们把返回的这个value值包装成promise的resolved值,我们可以测试一下:

    <script>
        async function fn() {
            return 1;
        }
        fn().then((data) => {
            console.log(data);
        })
    </script>
Output:1

也就是说上面的代码等同于下面的代码:

    <script>
        async function fn() {
            return Promise.resolve(1);
        }
    </script>

也就是说,async可以确保一个函数返回的是一个Promise对象,除了async关键字,还有一个await关键字,它只能在async函数中使用。

Await

关键词await可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行。

    <script>
        async function f() {
            let promise = new Promise((resolve, reject) => {
                setTimeout(() => resolve('done!'), 1000)
            })
            let result = await promise // 直到promise返回一个resolve值
            console.log(result);
        }
        f()
    </script>
Output:done!

在await的那一行,会“暂停”,直到promise处理完之后会重新恢复,resolve的值就会变成result,最终输出了done。await语句可以让promise更加的易于阅读。

async/await的应用

假如我们有一个操作,有很多步骤,并且每一步都是一个异步操作,后面的异步操作可能会依赖异步操作的结果,那么这种情况,我们用async/await可以很简单的解决:

    <script>
        function asyncTest(time){
            return new Promise(resolve => {
                setTimeout(resolve(time + 100), time);
            })
        };

        function stepOne(n){
            console.log("stepOne", n);
            return asyncTest(n);
        }

        function stepTwo(n1, n2){
            console.log("stepTwo", n1, n2);
            return asyncTest(n1 + n2);
        }

        function stepThree(n1, n2 , n3){
            console.log("stepThree", n1, n2, n3);
            return asyncTest(n1 + n2 + n3);
        }

        async function doAll(){
            let time1 = 500;
            let time2 = await stepOne(time1);
            let time3 = await stepTwo(time1, time2);
            let res = await stepThree(time1, time2, time3);
            console.log("res:", res);
        }

        doAll();
    </script>
Output:
stepOne 500
stepTwo 500 600
stepThree 500 600 1200
res: 2400
错误处理

如果一个Promise正常的resolved的话,它会返回结果,但是如果rejected的了,它会抛出一个错误,就好像那一行有一个throw语句一样。

    <script>
        async function fn(){
            await Promise.reject(new Error("ERR"));
        }
    </script>

和下面的是一样的:

    <script>
        async function fn(){
            throw new Error("ERR");
        }
    </script>

在真实的使用场景中,promise在reject抛出错误之前可能需要一段时间,所以await将会等待,然后才抛出一个错误。
我们可以使用try-catch语句捕获错误,就像在正常抛出中处理异常一样:

    <script>
        async function fn() {
            try {
                let response = await fetch('http://www.abc/some')
            } catch (err) {
                console.log(err) 
            }
        }
        fn()
    </script>

如果我们不使用try-catch,然后async函数f()的调用产生的promise变成reject状态的话,我们可以添加.catch去处理它:

    <script>
        async function fn() {
            let response = await fetch('http://www.bac/some')
        }
        fn().catch((err) => {
            console.log(err);
        })
    </script>

总结

我们使用async/await 搭配 Promise可以很好的解决异步操作问题,写出更易于阅读的异步操作代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值