JavaScript Promise啟示錄,promise啟示錄

來源:互聯網
上載者:User

JavaScript Promise啟示錄,promise啟示錄

本篇,主要普及promise的用法。

一直以來,JavaScript處理非同步都是以callback的方式,在前端開發領域callback機制幾乎深入人心。在設計API的時候,不管是瀏覽器廠商還是SDK開發商亦或是各種類庫的作者,基本上都已經遵循著callback的套路。

近幾年隨著JavaScript開發模式的逐漸成熟,CommonJS規範順勢而生,其中就包括提出了Promise規範,Promise完全改變了js非同步編程的寫法,讓非同步編程變得十分的易於理解。

在callback的模型裡邊,我們假設需要執行一個非同步隊列,代碼看起來可能像這樣:

loadImg('a.jpg', function() {  loadImg('b.jpg', function() {    loadImg('c.jpg', function() {      console.log('all done!');    });  });});

這也就是我們常說的回調金字塔,當非同步任務很多的時候,維護大量的callback將是一場災難。當今Node.js大熱,好像很多團隊都要用它來做點東西以沾沾“洋氣”,曾經跟一個營運的同學聊天,他們也是打算使用Node.js做一些事情,可是一想到js的層層回調就望而卻步。

好,扯淡完畢,下面進入正題。

Promise可能大家都不陌生,因為Promise規範已經出來好一段時間了,同時Promise也已經納入了ES6,而且高版本的chrome、firefox瀏覽器都已經原生實現了Promise,只不過和現如今流行的類Promise類庫相比少些API。

所謂Promise,字面上可以理解為“承諾”,就是說A調用B,B返回一個“承諾”給A,然後A就可以在寫計劃的時候這麼寫:當B返回結果給我的時候,A執行方案S1,反之如果B因為什麼原因沒有給到A想要的結果,那麼A執行應急方案S2,這樣一來,所有的潛在風險都在A的可控範圍之內了。

上面這句話,翻譯成代碼類似:

var resB = B();var runA = function() {  resB.then(execS1, execS2);};runA();

只看上面這行代碼,好像看不出什麼特別之處。但現實情況可能比這個複雜許多,A要完成一件事,可能要依賴不止B一個人的響應,可能需要同時向多個人詢問,當收到所有的應答之後再執行下一步的方案。最終翻譯成代碼可能像這樣:

var resB = B();var resC = C();...var runA = function() {  reqB    .then(resC, execS2)    .then(resD, execS3)    .then(resE, execS4)    ...    .then(execS1);};runA();

在這裡,當每一個被詢問者做出不符合預期的應答時都用了不同的處理機制。事實上,Promise規範沒有要求這樣做,你甚至可以不做任何的處理(即不傳入then的第二個參數)或者統一處理。

好了,下面我們來認識下Promise/A+規範:

  • 一個promise可能有三種狀態:等待(pending)、已完成(fulfilled)、已拒絕(rejected)
  • 一個promise的狀態只可能從“等待”轉到“完成”態或者“拒絕”態,不能逆向轉換,同時“完成”態和“拒絕”態不能相互轉換
  • promise必須實現then方法(可以說,then就是promise的核心),而且then必須返回一個promise,同一個promise的then可以調用多次,並且回調的執行順序跟它們被定義時的順序一致
  • then方法接受兩個參數,第一個參數是成功時的回調,在promise由“等待”態轉換到“完成”態時調用,另一個是失敗時的回調,在promise由“等待”態轉換到“拒絕”態時調用。同時,then可以接受另一個promise傳入,也接受一個“類then”的對象或方法,即thenable對象。

可以看到,Promise規範的內容並不算多,大家可以試著自己實現以下Promise。

以下是筆者自己在參考許多類Promise庫之後簡單實現的一個Promise,代碼請移步promiseA。

簡單分析下思路:

建構函式Promise接受一個函數resolver,可以理解為傳入一個非同步任務,resolver接受兩個參數,一個是成功時的回調,一個是失敗時的回調,這兩參數和通過then傳入的參數是對等的。

其次是then的實現,由於Promise要求then必須返回一個promise,所以在then調用的時候會新產生一個promise,掛在當前promise的_next上,同一個promise多次調用都只會返回之前產生的_next

由於then方法接受的兩個參數都是可選的,而且類型也沒限制,可以是函數,也可以是一個具體的值,還可以是另一個promise。下面是then的具體實現:

Promise.prototype.then = function(resolve, reject) {  var next = this._next || (this._next = Promise());  var status = this.status;  var x;  if('pending' === status) {    isFn(resolve) && this._resolves.push(resolve);    isFn(reject) && this._rejects.push(reject);    return next;  }  if('resolved' === status) {    if(!isFn(resolve)) {      next.resolve(resolve);    } else {      try {        x = resolve(this.value);        resolveX(next, x);      } catch(e) {        this.reject(e);      }    }    return next;  }  if('rejected' === status) {    if(!isFn(reject)) {      next.reject(reject);    } else {      try {        x = reject(this.reason);        resolveX(next, x);      } catch(e) {        this.reject(e);      }    }    return next;  }};

 

這裡,then做了簡化,其他promise類庫的實現比這個要複雜得多,同時功能也更多,比如還有第三個參數——notify,表示promise當前的進度,這在設計檔案上傳等時很有用。對then的各種參數的處理是最複雜的部分,有興趣的同學可以參看其他類Promise庫的實現。

在then的基礎上,應該還需要至少兩個方法,分別是完成promise的狀態從pending到resolved或rejected的轉換,同時執行相應的回調隊列,即resolve()reject()方法。

到此,一個簡單的promise就設計完成了,下面簡單實現下兩個promise化的函數:

function sleep(ms) {  return function(v) {    var p = Promise();    setTimeout(function() {      p.resolve(v);    }, ms);    return p;  };};function getImg(url) {  var p = Promise();  var img = new Image();  img.onload = function() {    p.resolve(this);  };  img.onerror = function(err) {    p.reject(err);  };  img.url = url;  return p;};

由於Promise建構函式接受一個非同步任務作為參數,所以getImg還可以這樣調用:

function getImg(url) {  return Promise(function(resolve, reject) {    var img = new Image();    img.onload = function() {      resolve(this);    };    img.onerror = function(err) {      reject(err);    };    img.url = url;  });};

接下來(見證奇蹟的時刻),假設有一個BT的需求要這麼實現:非同步擷取一個json配置,解析json資料拿到裡邊的圖片,然後按順序隊列載入圖片,沒張圖片載入時給個loading效果

function addImg(img) {  $('#list').find('> li:last-child').html('').append(img);};function prepend() {  $('<li>')    .html('loading...')    .appendTo($('#list'));};function run() {  $('#done').hide();  getData('map.json')    .then(function(data) {      $('h4').html(data.name);      return data.list.reduce(function(promise, item) {        return promise          .then(prepend)          .then(sleep(1000))          .then(function() {            return getImg(item.url);          })          .then(addImg);      }, Promise.resolve());    })    .then(sleep(300))    .then(function() {      $('#done').show();    });};$('#run').on('click', run);

這裡的sleep只是為了看效果加的,可猛擊查看demo!當然,Node.js的例子可查看這裡。

在這裡,Promise.resolve(v)靜態方法只是簡單返回一個以v為肯定結果的promise,v可不傳入,也可以是一個函數或者是一個包含then方法的對象或函數(即thenable)。

類似的靜態方法還有Promise.cast(promise),產生一個以promise為肯定結果的promise;

Promise.reject(reason),產生一個以reason為否定結果的promise。

我們實際的使用情境可能很複雜,往往需要多個非同步任務穿插執行,並行或者串列同在。這時候,可以對Promise進行各種擴充,比如實現Promise.all(),接受promises隊列並等待他們完成再繼續,再比如Promise.any(),promises隊列中有任何一個處於完成態時即觸發下一步操作。

標準的Promise

可參考html5rocks的這篇文章JavaScript Promises,目前進階瀏覽器如chrome、firefox都已經內建了Promise對象,提供更多的操作介面,比如Promise.all(),支援傳入一個promises數組,當所有promises都完成時執行then,還有就是更加友好強大的異常捕獲,應對日常的非同步編程,應該足夠了。

第三方庫的Promise

現今流行的各大js庫,幾乎都不同程度的實現了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出來的大都是Deferred對象,以jQuery(Zepto類似)為例,實現上面的getImg()

function getImg(url) {  var def = $.Deferred();  var img = new Image();  img.onload = function() {    def.resolve(this);  };  img.onerror = function(err) {    def.reject(err);  };  img.src = url;  return def.promise();};

當然,jQuery中,很多的操作都返回的是Deferred或promise,如animateajax

// animate$('.box')  .animate({'opacity': 0}, 1000)  .promise()  .then(function() {    console.log('done');  });// ajax$.ajax(options).then(success, fail);$.ajax(options).done(success).fail(fail);// ajax queue$.when($.ajax(options1), $.ajax(options2))  .then(function() {    console.log('all done.');  }, function() {    console.error('There something wrong.');  });

jQuery還實現了done()fail()方法,其實都是then方法的shortcut。

處理promises隊列,jQuery實現的是$.when()方法,用法和Promise.all()類似。

其他類庫,這裡值得一提的是when.js,本身代碼不多,完整實現Promise,同時支援browser和Node.js,而且提供更加豐富的API,是個不錯的選擇。這裡限於篇幅,不再展開。

尾聲

我們看到,不管Promise實現怎麼複雜,但是它的用法卻很簡單,組織的代碼很清晰,從此不用再受callback的折磨了。

最後,Promise是如此的優雅!但Promise也只是解決了回調的深層嵌套的問題,真正簡化JavaScript非同步編程的還是Generator,在Node.js端,建議考慮Generator。

下一篇,研究下Generator。

github原文: https://github.com/chemdemo/chemdemo.github.io/issues/6


/** 在javascript中是什

你說的應該是這種形式:
/**
I am just comment text.
Leave me alone!
*/
在這兩組符號中可以加入多行文字,用於對代碼進行解釋說明,供自己或他人查閱。/*用於標識注釋的開始,*/用於標識結尾,中間的文字對代碼的執行不會有任何影響

實際上,一般看到的多行注釋是這種形式的:
/*(一個星號)
I am just comment text.
Leave me alone!
*/
可有些人出於個人習慣或其他考慮,可能會寫成:
/**(兩個星號)
* Comment line 1
* Comment line 2
* Comment line 3
*/
絕大部分情況下(除了當你需要用JSDoc等工具產生API文檔時),這兩種寫法沒有任何區別

值得注意的是,多行注釋不能嵌套,而且要盡量避免在多行注釋中使用Regex
 
一JAVASCRIPT的方案

把以下內容儲存在Html檔案就行了

<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>16進位轉換成10進位</title>
</head>
<script language="javascript">
function SplitStr(SrcStr){
var DesStr = "";
var Str = SrcStr.split(" ");
for(i=0;i<Str.length;i++){
DesStr = DesStr + HexToOcx(Str[i]) + " ";
}
msg.innerText = DesStr;
}
function HexToOcx(Hex){
var x = Hex.substr(0,1).toUpperCase();
var y = Hex.substr(1,1).toUpperCase();
return 16*GetOcx(x)+GetOcx(y);
}
function GetOcx(Hex){
switch (Hex){
case "A":
return 10;
case "B":
return 11;
case "C":
return 12;
case "D":
return 13;
case "E":
return 14;
case "F":
return 15;
default:
return parseInt(Hex);
}
}
</script>
<body>
<form id="form1" name="form1" method="post" action="">
<textarea name="src" id="src"></textarea><br />
<input type="button" name="button1" value="轉換" onclick="SplitStr(form1.src.value)" />
</form>
<div id="msg"></div>
</body>
</html>...餘下全文>>
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.