前端,如何更优雅的面对异步

本文是在阐述异步编程思想,让前端代码更易于维护,看起来更优雅!不是讲技术,但你若能耐心看完,一定会有收获。
本文是不断补充的,随着开发实践的越来越多,以及技术的不断发展,代码可以写的越来越优雅!

用 Promise 处理交互和异步

前端开发经常会遇到这样的场景:

当满足一定条件时,需要弹出一个模态框,以便接收用户的输入。然后根据不同的输入,进行不用的操作。(ps:这类场景太常见了)

看了很多人的js代码,我发现大多数人的设计是这样的:

modalModule{ 

  ……//其他代码。

  cancel(){

    closeModal();

  }

  ok(){

    handle(inputResult);

    closeModal();

  }  

}

parentModule{

  if(condition){

    call modalModule;

  }

}

这样设计,两个模块有严重的耦合:

我们虽然写了一个modal模块,可是这个模块好像是专门为parent模块设计的。因为怎么处理结果,是完全取决于parent模块的业务需求的! 显然这样一个被量身打造的“专用模块”,几乎无法被重用。遗憾的是,我看到过很多这样设计的代码——想象一下,整个项目有大量类似的modal模块,打开模态框,关闭,确认等的这些代码 完全一样,唯一不同的就是handle。你能想象这是灾难吗?

接下来,我们来换一种设计:(把模态交互设计成回调模式,让handle(inputResult)这部分在parentModule中执行!)

modalModule{ 

  ……//其他代码。

  cancel(){

    Promise.reject();

    closeModal();

  }

  ok(){

    Promise.resolve(inputResult);

    closeModal();

  }

  return Promise;

}

parentModule{

  if(condition){

    call modalModule .then(res=>handle(res));

  }

}

这样设计可以发现,modalModule就是一个通用的模块了,绿色部分就是回调,表示对返回用户输入结果的一个承诺(promise),至于怎么处理这个结果就和它无关了——modalModule变得简单单一了。

另外parentModule的功能更集中了,更方便阅读理解了。

(多运用这种设计,自己感受,领悟它的好处吧!)

多重异步时,函数式编程 避开层层嵌套的处理

——补充于: 2017-11-30 23:09:57

前面学会使用Promise(rxjs中有更强大的Observable),代码一下子优雅了很多,但还不够!
想一下这个场景:先验证数据有无过期,没有过期 创建一个班级,成功后为班级创建一个课程表,成功后取到按课程分类的数据,最终成功后展示列表,失败时给出提示,另外在这个过程中页面应当一直提示处理中 !

很多人是这样处理的:

    func() {
        loading("处理中...");
        checkData().then(() => {
            createClass().then(() => {
                createSchedule().then(() => {
                    getList().then(() => {
                        closeLoading();
               showInPage(); }, err
=> { closeLoading(); toaster("获取课程表失败!"); }); }, err => { closeLoading(); toaster("创建课程表失败"); }); }, err => { closeLoading(); toaster("创建班级失败!"); }); }, err => { closeLoading(); toaster("数据已过期!"); }); }

关于loading的控制少了一处都不行,会导致loading一直存在:异步处理,定义错误信息,关闭loading,弹出提示,揉在一起,稍有疏忽漏掉一个就报错!


现在带大家体验一下函数式编程:

    func2(resolve,reject){
        checkData().then(() => {
            createClass().then(() => {
                createSchedule().then(() => {
                    getList().then(() => {
                        resolve();
                    }, err => {
                        reject("获取课程表失败!");
                    });
                }, err => {
                    reject("创建课程表失败");
                });
            }, err => {
                reject("创建班级失败!");
            });
        }, err => {
            reject("数据已过期!");
        });

    }

    func() {
        loading("处理中...");
        new Promise((resolve,reject)=>{
            func2(resolve,reject);
        }).then(()=>{
            closeLoading();      
            showInPage();      
        },msg=>{
            closeLoading();
            toaster(msg||"处理失败!");
        });
    }

可以func2 中四个异步就是四个异步处理+定义错误信息,对于结束时的结果处理(处理只在一个地方,不用导出都是),在 func 中!
这段代码 易读,易改,不易出错! 

async 和 await + Promise链式调用

补充于:2019年12月8日18:20:44

现在看来上面的代码写的太糟糕了,先别说明明有了异步,为啥还要用函数式调用,至少存在回调地狱的问题!

es2017的 async await 和 es2015的Promise链式调用 这两个特性对于解决该问题,非常有效,瞬间让你的代码可读性增强,非常优雅。请看修改: 

async function func2() {
    await checkData().catch(() => {
        return Promise.reject("数据已过期!")
    })
    await createClass().catch(() => {
        return Promise.reject("创建班级失败!")
    })
    await createSchedule().catch(() => {
        return Promise.reject("创建课程表失败!")
    })
    await getList().catch(() => {
        return Promise.reject("获取课程表失败!")
    })
}

function func() {
    loading("处理中...")
    func2().then(() => {
        showInPage()
    }).catch(msg => {
        toaster(msg || "处理失败!");
    }).finally(() => {
        closeLoading()
    })
}    

   

posted @ 2017-05-26 14:59  enne5w4  阅读(811)  评论(0编辑  收藏  举报