资讯专栏INFORMATION COLUMN

Promise到底解决了什么问题?

yibinnn / 2008人阅读

摘要:我的博客大家都知道解决了回调地狱的问题。这就是异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的。在与第三方团队沟通之后问题得到了解决。这不但使代码变得臃肿不堪,还进一步加剧了可读性的问题。的特征保证了可以解决信任问题。

我的github博客 https://github.com/zhuanyongxigua/blog

大家都知道Promise解决了回调地狱的问题。说到回调地狱,很容易想到下面这个容易让人产生误解的图片

可回调地狱到底是什么?它到底哪里有问题?是因为嵌套不好看还是读起来不方便?

首先我们要想想,嵌套到底哪里有问题?

举个例子:

function a() {
  function b() {
    function c() {
      function d() {}
      d();
    }
    c();
  }
  b();
}
a();

这也是嵌套,虽然好像不是特别美观,可我们并不会觉得这有什么问题吧?因为我们经常会写出类似的代码。

在这个例子中的嵌套的问题仅仅是缩进的问题,而缩进除了会让代码变宽可能会造成读代码的一点不方便之外,并没有什么其他的问题。如果仅仅是这样,为什么不叫“缩进地狱”或“嵌套地狱”?

把回调地狱完全理解成缩进的问题是常见的对回调地狱的误解。要回到“回调地狱”这个词语上面来,它的重点就在于“回调”,而“回调”在JS中应用最多的场景当然就是异步编程了。

所以,“回调地狱”所说的嵌套其实是指异步的嵌套。它带来了两个问题:可读性的问题和信任问题。

可读性的问题

这是一个在网上随便搜索的关于执行顺序的面试题:

for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(new Date, i);
  }, 1000);
}

console.log(new Date, i);

答案是什么大家自己想吧,这不是重点。重点是,你要想一会儿吧?

一个整洁的回调:

listen( "click", function handler( evt){ 
  setTimeout( function request(){ 
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
  }, 500); 
});

如果异步的嵌套都是这样干净整洁,那“回调地狱”给程序猿带来的伤害马上就会减少很多。

可我们实际在写业务逻辑的时候,真实的情况应该是这样的:

listen( "click", function handler(evt){ 
  doSomething1();
  doSomething2();
  doSomething3();
  doSomething4();
  setTimeout( function request(){ 
    doSomething8();
    doSomething9();
    doSomething10();
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
    doSomething11();
    doSomething12();
    doSomething13();
  }, 500); 
  doSomething5();
  doSomething6();
  doSomething7();
});

这些“doSomething”有些是异步的,有些是同步。这样的代码读起来会非常的吃力,因为你要不停的思考他们的执行顺序,并且还要记在脑袋里面。这就是异步的嵌套带来的可读性的问题,它是由异步的运行机制引起的。

信任问题

这里主要用异步请求讨论。我们在做AJAX请求的时候,一般都会使用一些第三方的工具库(即便是自己封装的,也可以在一定程度上理解成第三方的),这就会带来一个问题:这些工具库是否百分百的可靠?

一个来自《YDKJS》的例子:一个程序员开发了一个付款的系统,它良好的运行了很长时间。突然有一天,一个客户在付款的时候信用卡被连续刷了五次。这名程序员在调查了以后发现,一个第三方的工具库因为某些原因把付款回调执行了五次。在与第三方团队沟通之后问题得到了解决。

故事讲完了,可问题真的解决了吗?是否还能够充分的信任这个工具库?信任依然要有,可完善必要的检查和错误处理势在必行。当我们解决了这个问题,由于它的启发,我们还会联想到其他的问题,比如没有调用回调。

再继续想,你会发现,这样的问题还要好多好多。总结一下可能会出现的问题:

回调过早(一般是异步被同步调用);

回调过晚或没有回调;

回调次数过多;

等等

加上了这些检查,强壮之后的代码可能是这样的:

listen( "click", function handler( evt){ 
  check1();
  doSomething1();
  setTimeout( function request(){ 
    check2();
    doSomething3();
    ajax( "http:// some. url. 1", function response( text){ 
      if (text == "hello") { 
        handler(); 
      } else if (text == "world") { 
        request(); 
      } 
    }); 
    doSomething4();
  }, 500); 
  doSomething2();
});

我们都清楚的知道,实际的check要比这里看起来的复杂的多,而且很多很难复用。这不但使代码变得臃肿不堪,还进一步加剧了可读性的问题。

虽然这些错误出现的概率不大,但我们依然必须要处理。

这就是异步嵌套带来的信任问题,它的问题的根源在于控制反转。控制反转在面向对象中的应用是依赖注入,实现了模块间的解耦。而在回调中,它就显得没有那么善良了,控制权被交给了第三方,由第三方决定什么时候调用回调以及如何调用回调。

一些解决信任问题的尝试

加一个处理错误的回调

function success(data) { 
  console. log(data); 
} 
function failure(err) { 
  console. error( err ); 
} 
ajax( "http:// some. url. 1", success, failure );

nodejs的error-first

function response(err, data) { 
  if (err) { 
    console. error( err ); 
  } 
  else { 
    console. log( data ); 
  } 
} 
ajax( "http:// some. url. 1", response );

这两种方式解决了一些问题,减少了一些工作量, 但是依然没有彻底解决问题。首先它们的可复用性依然不强,其次,如回调被多次调用的问题依然无法解决。

Promise如何解决这两个问题

Promise已经是原生支持的API了,它已经被加到了JS的规范里面,在各大浏览器中的运行机制是相同的。这样就保证了它的可靠。

如何解决可读性的问题

这一点不用多说,用过Promise的人很容易明白。Promise的应用相当于给了你一张可以把解题思路清晰记录下来的草稿纸,你不在需要用脑子去记忆执行顺序。

如何解决信任问题

Promise并没有取消控制反转,而是把反转出去的控制再反转一次,也就是反转了控制反转。

这种机制有点像事件的触发。它与普通的回调的方式的区别在于,普通的方式,回调成功之后的操作直接写在了回调函数里面,而这些操作的调用由第三方控制。在Promise的方式中,回调只负责成功之后的通知,而回调成功之后的操作放在了then的回调里面,由Promise精确控制。

Promise有这些特征:只能决议一次,决议值只能有一个,决议之后无法改变。任何then中的回调也只会被调用一次。Promise的特征保证了Promise可以解决信任问题。

对于回调过早的问题,由于Promise只能是异步的,所以不会出现异步的同步调用。即便是在决议之前的错误,也是异步的,并不是会产生同步(调用过早)的困扰。

var a = new Promise((resolve, reject) => {
  var b = 1 + c;  // ReferenceError: c is not defined,错误会在下面的a打印出来之后报出。
  resolve(true);
})
console.log(1, a);
a.then(res => {
  console.log(2, res);
})
.catch(err => {
  console.log(err);
})

对于回调过晚或没有调用的问题,Promise本身不会回调过晚,只要决议了,它就会按照规定运行。至于服务器或者网络的问题,并不是Promise能解决的,一般这种情况会使用Promise的竞态APIPromise.race加一个超时的时间:

function timeoutPromise(delay) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject("Timeout!");
    }, delay);
  });
}

Promise.race([doSomething(), timeoutPromise(3000)])
.then(...)
.catch(...);

对于回调次数太少或太多的问题,由于Promise只能被决议一次,且决议之后无法改变,所以,即便是多次回调,也不会影响结果,决议之后的调用都会被忽略。

参考资料:

You Don"t Know JS: Async & Performance

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97402.html

相关文章

  • 在非阻塞IO下的nodejs下的同步并行 ES6的 promise 从入门深入(一)

    摘要:我们先介绍一下中的的一些调用再结合的应用逐步深入。这就是一些简单的的调用看起来不多,但是靠这个真得解决了许多必须同步并行的环境本身是一个对象在开始支持。存在两个回调函数根据个人的需求进行处理。 什么是promise?为什么要在nodejs中使用promise?使用promise到底有什么好处呢?实在太多了,一一说来不如直接上实战。我们先介绍一下nodejs中的promise的一些调用....

    luffyZh 评论0 收藏0
  • 理解 JavaScript 的 async/await

    摘要:因为函数返回一个对象,所以可以用于等待一个函数的返回值这也可以说是在等函数,但要清楚,它等的实际是一个返回值。帮我们干了啥作个简单的比较上面已经说明了会将其后的函数函数表达式或的返回值封装成一个对象,而会等待这个完成,并将其的结果返回出来。 随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await。我第一次看到这组关键字并不是在 JavaSc...

    tracymac7 评论0 收藏0
  • 前端校招准备系列--js中的setTimeout到底什么

    摘要:浏览器是多进程的,而浏览器的内核渲染进程是多线程的。如果已经将回调函数放进任务队列,但是主线程正在执行一个非常耗时的任务,当这个任务执行完毕后,主线程去任务队列中取任务,这个时候,就会出现连续执行的情况,也就是说相当于失效了。 前言   在刷笔试题的时候,经常会碰到setTimeout的问题,只知道这个是设置定时器;但是考察的重点一般是在一个方法中包含了定时器,定时器中的打印和方法中打...

    Godtoy 评论0 收藏0
  • [译] 深入理解 Promise 五部曲:1. 异步问题

    摘要:当引擎开始执行一个函数比如回调函数时,它就会把这个函数执行完,也就是说只有执行完这段代码才会继续执行后面的代码。当条件允许时,回调函数就会被运行。现在,返回去执行注册的那个回调函数。 原文地址:http://blog.getify.com/promis... 在微博上看到有人分享LabJS作者写的关于Promise的博客,看了下觉得写得很好,分五个部分讲解了Promise的来龙去脉。从...

    CHENGKANG 评论0 收藏0
  • JavaScript Promise 告别异步乱嵌套

    摘要:回调函数少了还好,一旦多了起来而且必须讲究执行顺序的话,回调函数开始嵌套,那代码的恶心程度是相当不符合常人的线性思维的。 什么是Promise? 在说Promise之前, 不得不说一下,JavaScript的嵌套的回调函数 在JavaScript语言中, 无论是写浏览器端的各种事件回调、ajax回调,还是写Node.js上的业务逻辑,不得不面对的问题就是各种回调函数。回调函数少了还好,...

    Mr_houzi 评论0 收藏0

发表评论

0条评论

yibinnn

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<