在开发中,有时候需要发送大量的请求,然后经常会面临嵌套回调的问题,也就是在一个回调中嵌入一个回调,导致代码逐层缩进非常强大,如下代码所示:ajax({url:"/list",type:"GET",success:function(data){appendToDOM(data);ajax({url:"/update",type:"POST",success:function(data){util.toast("Success!");})});}});这种代码看起来有点难,这种异步回调通常可以用Promise优化,可以把上面的代码改成:newPromise(resolve=>{ajax({url:"/list",type:"GET",success:data=>resolve(data);})}).then(data=>{appendToDOM(data);ajax({url:"/update",type:"POST",success:function(data){util.toast("Successfully!");})});});Promise提供了一个resolve,方便异步结束时的Notify,但是本质还是一样的,依旧使用回调,但是回调放在then中。当需要获取多个异步数据时,可以使用Promise.all解决:letorderPromise=newPromise(resolve=>{ajax("/order","GET",data=>resolve(data));});letuserPromise=newPromise(resolve=>{ajax("/user","GET",data=>resolve(data));});Promise.all([orderPromise,userPromise]).then(values=>{letorder=值[0],用户=值[1];});但是这里也使用了回调。有更优雅的解决方案吗?ES7的await/async允许使用与同步代码相同的方式编写异步回调。第一个嵌套回调的例子可以用await改成如下代码://使用await获取异步数据letleadList=awaitnewPromise(resolve=>{ajax({url:"/list",type:"GET",success:data=>resolve(data);});});//await让代码像瀑布一样自然写下来appendToDom(leadList);ajax({url:"/update",type:"POST",成功:()=>util.toast("成功");});await可以让代码像瀑布一样自然地写下来。第二个例子:获取多个异步数据,可以改成这样:"/user",data=>resolve(data)));//dosth.withorder/user就像从本地获取数据一样,所以不需要设置回调函数。除了发送请求,Await还适用于其他异步场景。比如我在创建订单前,弹出一个小框询问用户要创建什么类型的订单,然后弹出具体的订单设置框,所以按这里正常的思路是传一个按钮回调点击函数,如下图:但其实可以使用await来解决,如下代码所示:letquoteHandler=require("./quote");//弹出框询问用户并获取用户的SelectletcreateType=awaitquoteHandler.confirmCreate();quote返回一个Promise,监听点击事件,并传递createType:letquoteHandler={confirmCreate:function(){dialog.showDialog({contentTpl:tpl,className:"confirm-create-quote"});let$quoteDialog=$(".confirm-create-quoteform")[0];returnnewPromise(resolve=>{$(form.submit).on("click",function(event){resolve(form.createType.value);});});}}这样外部调用者就可以使用await而无需传递点击事件的回调函数。但是需要注意await的一次性执行特性。与回调函数相比,await的执行是一次性的。比如你监听点击事件,然后使用await,那么点击事件只会执行一次,因为代码是从上往下执行的,所以当你点击之后想报错的时候,还是可以的如果你继续修改提交,就不能使用await了。另外,使用await获取异步数据。如果出错,resolve成功的不会执行,后面的代码也不会执行,所以请求失败时基本逻辑不会有问题。要在babel中使用await,需要:(1)安装一个Node包npminstall–save-devbabel-plugin-transform-async-to-generator(2)在项目根目录添加一个.babelrc文件,内容就是:{"plugins":["transform-async-to-generator"]}(3)使用的时候,先引入一个模块require("babel-polyfill");那么就可以愉快的使用ES7的await了。async关键字需要在usingawait的函数前加上,如下:asyncshowOrderDialog(){//获取创建类型letcreateType=awaitquoteHandler.confirmCreate();//获取旧订单数据letorderInfo=awaitorderHandler.getOrderData();}再举个例子:用await实现JS版的sleep函数,因为没有提供native线程sleep函数,如下代码所示:functionssleep(time){returnnewPromise(resolve=>setTimeout(()=>resolve(),time));}asyncfunctionstart(){awaitsleep(1000);}start();Babel的await实现转化为ES6生成器,关键代码如下:while(1){switch(_context.prev=_context.next){case0:_context.next=2;//sleep返回一个Promise对象returnsleep(1000);case2:case"end":return_context.stop();}}而babel的generator也是用ES5实现,什么是生成器?如下如图:生成器是通过function*定义的,生成器的下一个函数每执行一次,就会返回当前生成器中yield返回的值,然后生成器的iterator会返回一个步骤,直到所有产量都完成。有兴趣的可以继续研究babel是如何将ES7转ES5的。据说原生实现还是直接基于Promise。使用await还有一个好处就是可以直接try-catch捕获异步进程抛出的异常,因为我们不能直接在异步回调中捕获异常,如下代码:letquoteHandler={confirmCreate:function(){$(form.submit).on("click",function(event){//这里会抛出undefined异常:undefined被访问value属性callback(form.notFoundInput.value);});}}try{//exceptioncannotcatchherequoteHandler.confirmCreate();}catch(e){}上面的try-catch是没有办法捕获到异常的,因为try里面的代码已经执行过了,执行过程中没有异常,所以它不能在这里被捕获。如果使用Promise,一般会使用Promise链的catch:letquoteHandler={confirmCreate:function(){returnnewPromise(resolve=>{$(form.submit).on("click",function(event){//这里会抛出未定义的异常:访问未定义的value属性resolve(form.notFoundInput.value);});});}}quoteHandler.confirmCreate().then(createType=>{}).catch(e=>{//这里可以捕获异常});而使用await,我们可以直接使用同步catch,就好像真的变了已经同步执行了:try{createType=awaitquoteHandler.confirmCreate("order");}catch(e){console.log(e);return;}总之,使用await可以让代码少写很多嵌套,非常方便逻辑处理,享受丝般顺滑。原文链接:https://fed.renren.com/2017/10/31/await/【本文为专栏作者“人人网FED”原创稿件,转载请联系原作者获得授权】点击在这里看文章作者更多好文章
