Promise到底解决了什么问题?

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

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

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

举个例子:

 
  1. function a() {

  2. function b() {

  3. function c() {

  4. function d() {}

  5. d();

  6. }

  7. c();

  8. }

  9. b();

  10. }

  11. a();

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

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

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

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

可读性的问题

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

 
  1. for (var i = 0; i < 5; i++) {

  2. setTimeout(function() {

  3. console.log(new Date, i);

  4. }, 1000);

  5. }

  6.  
  7. console.log(new Date, i);

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

一个整洁的回调:

 
  1. listen( "click", function handler( evt){

  2. setTimeout( function request(){

  3. ajax( "http:// some. url. 1", function response( text){

  4. if (text == "hello") {

  5. handler();

  6. } else if (text == "world") {

  7. request();

  8. }

  9. });

  10. }, 500);

  11. });

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

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

 
  1. listen( "click", function handler(evt){

  2. doSomething1();

  3. doSomething2();

  4. doSomething3();

  5. doSomething4();

  6. setTimeout( function request(){

  7. doSomething8();

  8. doSomething9();

  9. doSomething10();

  10. ajax( "http:// some. url. 1", function response( text){

  11. if (text == "hello") {

  12. handler();

  13. } else if (text == "world") {

  14. request();

  15. }

  16. });

  17. doSomething11();

  18. doSomething12();

  19. doSomething13();

  20. }, 500);

  21. doSomething5();

  22. doSomething6();

  23. doSomething7();

  24. });

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

信任问题

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

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

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

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

  • 回调过早(一般是异步被同步调用);
  • 回调过晚或没有回调;
  • 回调次数过多;
  • 等等

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

 
  1. listen( "click", function handler( evt){

  2. check1();

  3. doSomething1();

  4. setTimeout( function request(){

  5. check2();

  6. doSomething3();

  7. ajax( "http:// some. url. 1", function response( text){

  8. if (text == "hello") {

  9. handler();

  10. } else if (text == "world") {

  11. request();

  12. }

  13. });

  14. doSomething4();

  15. }, 500);

  16. doSomething2();

  17. });

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

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

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

一些解决信任问题的尝试

加一个处理错误的回调

 
  1. function success(data) {

  2. console. log(data);

  3. }

  4. function failure(err) {

  5. console. error( err );

  6. }

  7. ajax( "http:// some. url. 1", success, failure );

nodejs的error-first

 
  1. function response(err, data) {

  2. if (err) {

  3. console. error( err );

  4. }

  5. else {

  6. console. log( data );

  7. }

  8. }

  9. ajax( "http:// some. url. 1", response );

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

Promise如何解决这两个问题

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

如何解决可读性的问题

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

如何解决信任问题

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

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

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

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

 
  1. var a = new Promise((resolve, reject) => {

  2. var b = 1 + c; // ReferenceError: c is not defined,错误会在下面的a打印出来之后报出。

  3. resolve(true);

  4. })

  5. console.log(1, a);

  6. a.then(res => {

  7. console.log(2, res);

  8. })

  9. .catch(err => {

  10. console.log(err);

  11. })

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

 
  1. function timeoutPromise(delay) {

  2. return new Promise(function(resolve, reject) {

  3. setTimeout(function() {

  4. reject("Timeout!");

  5. }, delay);

  6. });

  7. }

  8.  
  9. Promise.race([doSomething(), timeoutPromise(3000)])

  10. .then(...)

  11. .catch(...);

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

参考资料:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值