有Promise,不會搞大肚子

海興發表於2016-07-03

Promise是專門給非同步計算用的物件,它表示一個現在還沒結果,但將來會給你算出來結果來的操作。

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

權尾珍

憋說話,看圖....

enter image description here

圖片中的姑娘是韓國搞笑藝人權尾珍,其實她是一位諧星,體重曾經高達103公斤!後來參加了韓國一個減肥真人秀的節目,用三年時間減重51公斤。瘦下來後,權尾珍通過雜誌專欄、電臺、部落格向大眾分享了她的減肥經驗,“權式減肥法”也開始風靡韓國。

菲涅爾透鏡

你們小時候是不是都拿放大鏡照過螞蟻?!所以你們應該都知道什麼叫凸透鏡吧?菲涅爾透鏡也是一種凸透鏡,不過它和一般的凸透鏡不一樣。它不喜歡普通透鏡那種胖胖的身材,所以立志減肥,成功瘦身成了一枚纖細的透鏡。至於它為什麼叫菲涅爾透鏡,跟科學界的其他產物一樣,因為它是由法國物理學家奧古斯汀.菲涅爾(Augustin.Fresnel)發明的。那是在1822年,菲涅爾第一次把這種透鏡用在了燈塔上。哦,估計也只有用在燈塔上才能體現它身材上的優勢。

菲涅爾透鏡瘦身成功並不是靠慢跑,也不是靠舉鐵,更不是靠節食。它主要靠抽脂......科學總是有道理的,我們都知道,光的折射是發生在兩種介質相互接觸的表面的,(比如玻璃透鏡的表面),在一種介質內部是不會發生折射的,所以菲涅爾保留了透鏡表面的彎曲度,把裡面的東西掏空,把鼓鼓的透鏡壓扁,變成菲涅爾透鏡。理論上是像下面這樣:

enter image description here

按照這個理論做出來的菲涅爾透鏡是這樣的:

enter image description here

很瘦,但依然聚光。

有承諾,沒肚子

小姑娘要減肥大家已經司空見慣了。可是你看,連一塊透鏡都知道要瘦身,我們怎麼還好意思不鍛鍊呢?你可能會說我們碼農天天加班沒時間,那麼問題來了。既然你把時間都用在了程式碼上,怎麼還好意思讓自己的程式碼裡到處都是挺著大肚子的Callback呼叫鏈呢?不就是要按順序執行幾個非同步計算嗎?有必要非把自己程式碼的肚子搞大嗎?作為一名負責任的碼農,該採取措施還是要採取措施的,把Promise用起來,去掉Pyramid of Doom,還程式碼一個平坦的小腹,好不好?

enter image description here

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開發者工具的控制檯中執行,得到的結果是這樣的:

enter image description here

上面的程式碼看起來還不太直觀,我們來分解一下:

/**
 * 執行非同步操作的函式,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中去,回撥函式的呼叫鏈就變成平坦的了。

enter image description here

當然,我們都知道,健身不光能讓體型好看,還有很多額外的好處,比如血壓血脂膽固醇什麼的。用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課程可以看看。

還有我的公眾號,也可以關注一下: enter image description here

相關文章