Es6

如何优雅的使用promise来解决异步

2020-11-27 10:08发布

我们知道,在promise对象的then方法可以处理onfulfilledonrejected两个事件监听回调,但是我们一般采用catch来处理onrejected的监听回调,因为catch可以捕获部分程序异常;有利于程序的健壮性。例如:     

  function getBanner() {
            let p = new Promise((resolve, reject) => {
                $.ajax({
                    type: "GET",
                    url: "http://localhost:3000/api/index/banner",
                    success: function (response) {
                        resolve(response);
                    },
                    error: function (err) {
                        reject(err);
                    }
                });
            });
            return p;
        }
 
        let p = getBanner()
            .then(rst => {
                return rst;
            })
            .catch(err => {
                console.log(err);
            });

 

我们通过jQueryajax来向服务器发起轮播图数据的请求,上面代码若是正确的执行则会进入then方法里处理,若是异常的,也就是说必然进入catch环节,这代码看起来并没有什么,好像也并不复杂。

但是,如果在异步请求过程中出现了几个请求直接有依赖关系,则使用这种解决方案就复杂得多了。例如:     

   $.ajax({
            url: "http://www.ujiuye.tech/:3000/api/firstCategory", // 所有一级分类
            dataType: "json",
            success(res) {
                $.ajax({
                    url: `http://www.ujiuye.tech/:3000/api/secondCategory`, // 传递一级ID换取下属的二级分类列表
                    data: {
                        firstId: res['list'][0][0]['firstId']
                    },
                    dataType: "json",
                    success(res2) {
                        $.ajax({
                            url: `http://www.ujiuye.tech/:3000/api/thiredCategory`, // 传递二级分类ID, 获取下属的三级分类列表
                            data: {
                                secondId: res2['list'][0]['secondId']
                            },
                            dataType: "json",
                            success(res3) {
                                $.ajax({
                                    url: `http://www.ujiuye.tech/:3000/api/categoryList`,// 传递三级分类ID, 获取下属的商品数据列表
                                    data: {
                                        thiredId: res3['list'][0]['thiredId']
                                    },
                                    dataType: "json",
                                    success(result) {
                                        console.log(result);
                                    }
                                })
                            }
                        })
                    }
                })
            }
        })

在小U商城项目中的商品列表页面,我们同时要发起四个请求来分别获取:一级分类、二级分类、三级分类和商品,那么这是时候利用回调函数会出现回调地狱的现象,即使是使用promise来优化,也会出现大量的代码嵌套,这样的代码会容易让人疑惑,而且也不利于后续的开发维护。所以我们可以使用async...await来优化这些。

例如上面请求轮播图的例子,使用asyncawait来优化之后:

function
 getBanner() {
            let p = new Promise((resolve, reject) => {
                $.ajax({
                    type: "GET",
                    url: "http://localhost:3000/api/index/banner",
                    success: function (response) {
                        resolve(response);
                    },
                    error: function (err) {
                        reject(err);
                    }
                });
            });
            return p;
        }
 
        async function getData(){
            let data=await getBanner();
            console.log(data);
        }

 

这样的代码相比于上面的代码要简化很多,但是也有弊端,由于await只能接收promise的成功结果,也就是说,若上面代码出现了异常,则代码会中断执行。作为一个优秀的开发人员肯定不希望代码崩掉,那么该如何解决异常呢,有两种方案:一是采用try..catch来捕获异常,另外是使用catch

        async function getData() {
            try {
                let data = await getBanner();
                console.log(data);
            } catch (e) {
                console.log(e);
            }
        }
 
        //或者
        async function getData() {
            let data = await getBanner().catch(e => {
                console.log(e);
            })
            console.log(data);
        }

但这两种方案都又会出现嵌套,特别是若发起一些负责的请求(例如上面回调地狱的情况),则代码依然非常复杂,那么有没有更好的解决方案呢。答案是,必须有啊。在项目开发过程中,我们经常使用await-to-js的技术来解决这个问题。上干货:

 function to(p) {
            return p
                .then(data => [null, data])
                .catch(err => [err, null]);
        }

其实这个方案依然是利用promise的链式调用原理来解决的。那么使用,最后代码为:

     function to(p) {
            return p
                .then(data => [null, data])
                .catch(err => [err, null]);
        }
 
        async function getData() {
            let [err, data] = await to(getBanner())
        }

利用这个方案,大家发现,代码量不仅大大的减少,而且兼容性更加友好。特别是用来处理回调地狱的情况,简直不要太舒服。