有Promise,不会搞大肚子
发布于 8 年前 作者 wuhaixing 6077 次浏览 来自 分享

Promise是专门给异步计算用的对象,它表示一个现在还没结果,但将来会给你算出来结果来的操作。

没有大肚子的人生是更好的人生

权尾珍

憋说话,看图…

权尾珍减肥前后

图片中的姑娘是韩国搞笑艺人权尾珍,其实她是一位谐星,体重曾经高达103公斤!后来参加了韩国一个减肥真人秀的节目,用三年时间减重51公斤。瘦下来后,权尾珍通过杂志专栏、电台、博客向大众分享了她的减肥经验,“权式减肥法”也开始风靡韩国。

菲涅尔透镜

你们小时候是不是都拿放大镜照过蚂蚁?!所以你们应该都知道什么叫凸透镜吧?菲涅尔透镜也是一种凸透镜,不过它和一般的凸透镜不一样。它不喜欢普通透镜那种胖胖的身材,所以立志减肥,成功瘦身成了一枚纤细的透镜。至于它为什么叫菲涅尔透镜,跟科学界的其他产物一样,因为它是由法国物理学家奥古斯汀.菲涅尔(Augustin.Fresnel)发明的。那是在1822年,菲涅尔第一次把这种透镜用在了灯塔上。哦,估计也只有用在灯塔上才能体现它身材上的优势。

菲涅尔透镜瘦身成功并不是靠慢跑,也不是靠举铁,更不是靠节食。它主要靠抽脂…科学总是有道理的,我们都知道,光的折射是发生在两种介质相互接触的表面的,(比如玻璃透镜的表面),在一种介质内部是不会发生折射的,所以菲涅尔保留了透镜表面的弯曲度,把里面的东西掏空,把鼓鼓的透镜压扁,变成菲涅尔透镜。理论上是像下面这样:

160701-菲涅尔透镜原理.jpg

按照这个理论做出来的菲涅尔透镜是这样的:

160701-菲涅尔透镜实物.jpg

很瘦,但依然聚光。

有承诺,没肚子

小姑娘要减肥大家已经司空见惯了。可是你看,连一块透镜都知道要瘦身,我们怎么还好意思不锻炼呢?你可能会说我们码农天天加班没时间,那么问题来了。既然你把时间都用在了代码上,怎么还好意思让自己的代码里到处都是挺着大肚子的Callback调用链呢?不就是要按顺序执行几个异步计算吗?有必要非把自己代码的肚子搞大吗?作为一名负责任的码农,该采取措施还是要采取措施的,把Promise用起来,去掉Pyramid of Doom,还代码一个平坦的小腹,好不好?

160702-callback-doom.jpg

Promise

Promise是专门给异步计算用的对象,很早之前就作为神技在几大门派间流传,ES6之后被纳入官库。也就是说现在可以像下面这样直接在代码中定义:

    new Promise(function(resolve,reject) {
        console.log("Start");	  
        window.setTimeout(function() {
          resolve();
        }, 2000);
        console.log("Waitting");	  
    }).then(function() {
        console.log("Finished!");
    });

把上面的代码复制到Chrome开发者工具的控制台中执行,得到的结果是这样的:

160702_Promise.png

上面的代码看起来还不太直观,我们来分解一下:

    /**
     * 执行异步操作的函数,2秒后调用
     * 它的回调函数resolve
     */
    function asyncMission(resolve) {
       window.setTimeout(function() {
          resolve();
        }, 2000);
    } 
    /**
     * 发起异步操作的函数
     *  @param  {function} resolve 
     *       异步操作成功时调用的回调函数
     *  @param  {function} reject 
     *       异步操作失败时调用的回调函数
     */
    var executor = function(resolve,reject) {
        console.log("Start");	
        asyncMission(resolve);
        console.log("Waitting");
    }
    /**
     * 创建一个Promise对象,参数为发起
     * 异步操作的那个函数executor
     */
    var promiseMission = new Promise(executor);     
    /**
     * 神秘的resolve终于现身了!
     * 你可以把方法then的第一个参数
     * 当作resolve
     */   
    promiseMission.then(function() {
        console.log("Finished!");
    });

看完上面这段代码,再来看看MDN上对Promise的定义

The Promise object is used for asynchronous computations. A Promise represents an operation that hasn’t completed yet, but is expected in the future.

看不懂英文没关系,这不正好我在嘛!给你翻译一下,用中国话说,定义一个Promise就相当于老大跟你说:“你出趟远门,老子有个异步计算的差事要交给你办,事成之后,________________"。看到没,给了张空白支票!!!而then就是让你填那个空的方法,事成之后你想干什么,告诉then就成了。

不过你应该知道的,老大都是很有原则的人,不会像我上面写的代码一样,只告诉你事成之后可以怎么样。说完好处,他的脸上一定还会浮现出讳莫如深的、蛋蛋的忧伤,告诉你办不成应该怎样。所以一个完整的Promise应该是这样的:

    var p1 = new Promise(
        function(resolve, reject) {            
            window.setTimeout(
                function() {
                    resolve(Math.random());
                }, 2000);
        }
    );

    p1.then(
          // resolve,val就是上面那个Math.random()的值
          function(val) {
             console.log(val);
          })
       .catch(
          // reject
          function(reason) {
             console.log('搞砸了,',reason);
          });

Promise一直都知道,在经过漫长的pending之后,事情总会有settled的时候。但settled的结果,有可能是fulfilled,也有可能是rejected。所以我们可以用then告诉它事成之后怎么办,也可以用catch告诉它失败了怎么办。关于Promise,我要说的这么多;不过关于then,还有很多话要说。

then(what)?

then是减掉大肚子的关键,看清了then是什么,就算是掌握了Promise的奥义。来,请看MDN中对then的定义

The then() method returns a Promise. It takes two arguments: callback functions for the success and failure cases of the Promise.

所有的秘密都在第一句话里…

在前面所有的代码里,都隐藏着一个很容易被忽视的事实,then也是有返回值的,而且它返回的就是Promise!不要忘了,我们看到的那个函数只是它的参数,不管它的参数有没有返回值,then都会返回一个Promise。我们可以简单地把then的实现理解成下面这个样子:

     then(resolve,reject) {
       var val = resolve();
       return new Promise(
          function(_resolve,_reject) {
             _resolve(val)
       });
     }

所以整个故事大概是这样的:开始创建Promise的时候,我们只知道它的参数是一个函数,而这个函数的参数是两个回调函数。这两个回调函数一个是在Promise被fulfilled时调用的resolve,一个是Promise被rejected时调用的reject;但这两个回调函数具体长什么我们并不知道。然后then登场了,它的参数就是那个神秘的resolve回调函数。哦,对,then还可以用第二个参数指出reject是谁,但那是2B码农的写法,优雅的程序猿轻易不会露出那么急赤白脸的吃相。reject应该作为catch的参数出现。

既然then返回的是Promise,那then(和catch)之后就可以接着then(和catch),然后再then(和catch),这样callback的调用就可以从回调函数里提出来,放到then中去,回调函数的调用链就变成平坦的了。

160702_promises_chain.png

当然,我们都知道,健身不光能让体型好看,还有很多额外的好处,比如血压血脂胆固醇什么的。用Promise写代码也有很多额外的好处,我就不说了,留着你自己慢慢体会吧。

最后,为了感谢你有看这么长时间的耐心,整点干货。

PyramidOfDoom VS chainedThen

憋说话,看代码:

    function mission(duration,callback) {
      console.log(`Mission ${duration/1000} Start at ${Date.now()}`);
      window.setTimeout(function() {
        console.log(`Waitting Mission ${duration/1000}`);
        callback(duration);
      }, duration);
    }

    (function pyramidOfDoom() {
      mission(1000,function() {
        console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
        mission(2000,function() {
          console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
          mission(3000,function() {
            console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
            mission(4000,function() {
              console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
              mission(5000,function() {
                console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
                mission(6000,function() {
                  console.log("All missions Completed!");
                })
              })
            })
          })
        })
      });
    })();

再看这个:

    function promiseMission(duration) {
      console.log(`Mission ${duration/1000} Start at ${Date.now()}`);
      return new Promise(function(resolve,reject) {
        window.setTimeout(function() {
          resolve(duration);
          console.log(`Waitting Mission ${duration/1000}`);
        }, duration);
      });
    }

    promiseMission(1000)
      .then(function(duration) {
        console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
        return missionPromise(duration+1000);
      })
      .then(function(duration) {
        console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
        return missionPromise(duration+1000);
      })
      .then(function(duration) {
        console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
        return missionPromise(duration+1000);
      })
      .then(function(duration) {
        console.log(`Mission ${duration/1000} Complete at ${Date.now()} after ${duration}`);
        return missionPromise(duration+1000);
      })
      .then(function() {
        console.log("All missions Completed!");
      })

最后再安利一个教程,优达学院有个免费的JavaScript Promise课程可以看看。还有我的公众号,也可以关注一下:

qrcode_for_gh_627839794f0f_430 .jpg

10 回复

then then then then then then then then then then then then …

写起来也是像拉。。。,wtf

文章走心 支持一下

@kentkwan 感谢支持

觉得promise的好处,关键是控制的反转,错误处理好过一些

@fundon 是觉得callback用起来更爽?还是搞不懂promise所以很排斥?

@fundon 好像很有道理的样子

promise 是一种设计模式,他不应该只用在针对异步的简单处理

只使用它来处理异步问题,会导致可读性越来越差(滥用)

像这种代码,用 co.js + yield 更合适

@wuhaixing 面对复杂业务逻辑,promise 也不是银弹。

有理有据,令人信服

@fundon 这个说得确实是太有道理了

回到顶部