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