国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript Promise:去而復返

Lowky / 2336人閱讀

摘要:的比較接近,如下創建的構造器接受一個函數作為參數,它會傳遞給這個回調函數兩個變量和。在回調函數中做一些異步操作,成功之后調用,否則調用。另外還要注意,也沒有遵循給否定回調函數傳遞對象的慣例。當你從的回調函數返回的時候,這里有點小魔法。

原文:http://www.html5rocks.com/en/tutorials/es6/promises/
作者:Jake Archibald
翻譯:Amio

女士們先生們,請準備好迎接 Web 開發歷史上一個重大時刻……

[鼓聲響起]

  

JavaScript 有了原生的 Promise!

[漫天的煙花綻放,人群沸騰了]

這時候你大概是這三種人之一:

你的身邊擁擠著歡呼的人群,但是你卻不在其中,甚至你還不大清楚“Promise”是什么。你聳聳肩,煙花的碎屑在你的身邊落下。這樣的話,不要擔心,我也是花了多年的時間才明白 Promise 的意義,你可以從入門簡介:他們都在激動什么?開始看起。

你一揮拳!太贊了對么!你已經用過一些 Promise 的庫,但是所有這些第三方實現在API上都略有差異,JavaScript 官方的 API 會是什么樣子?看這里:Promise 術語!

你早就知道了,看著那些歡呼雀躍的新人你的嘴角泛起一絲不屑的微笑。你可以安靜享受一會兒優越感,然后直接去看 API 參考吧。

他們都在激動什么?

JavaScript 是單線程的,這意味著任何兩句代碼都不能同時運行,它們得一個接一個來。在瀏覽器中,JavaScript 和其他任務共享一個線程,不同的瀏覽器略有差異,但大體上這些和 JavaScript 共享線程的任務包括重繪、更新樣式、用戶交互等,所有這些任務操作都會阻塞其他任務。

作為人類,你是多線程的。你可以用多個手指同時敲鍵盤,也可以一邊開車一遍電話。唯一的全局阻塞函數是打噴嚏,打噴嚏期間所有其他事務都會暫停。很煩人對么?尤其是當你開著車打著電話的時候。我們都不喜歡這樣打噴嚏的代碼。

你應該會用事件加回調的辦法來處理這類情況:

var img1 = document.querySelector(".img-1");

img1.addEventListener("load", function() {
  // woo yey image loaded
});

img1.addEventListener("error", function() {
  // argh everything"s broken
});

這樣就不打噴嚏了。我們添加幾個監聽函數,請求圖片,然后 JavaScript 就停止運行了,直到觸發某個監聽函數。

上面的例子中唯一的問題是,事件有可能在我們綁定監聽器之前就已經發生,所以我們先要檢查圖片的complete屬性:

var img1 = document.querySelector(".img-1");

function loaded() {
  // woo yey image loaded
}

if (img1.complete) {
  loaded();
}
else {
  img1.addEventListener("load", loaded);
}

img1.addEventListener("error", function() {
  // argh everything"s broken
});

這樣還不夠,如果在添加監聽函數之前圖片加載發生錯誤,我們的監聽函數還是白費,不幸的是 DOM 也沒有為這個需求提供解決辦法。而且,這還只是處理一張圖片的情況,如果有一堆圖片要處理那就更麻煩了。

事件不是萬金油

事件機制最適合處理同一個對象上反復發生的事情—— keyup、touchstart 等等。你不需要考慮當綁定監聽器之前所發生的事情,當碰到異步請求成功/失敗的時候,你想要的通常是這樣:

img1.callThisIfLoadedOrWhenLoaded(function() {
  // loaded
}).orIfFailedCallThis(function() {
  // failed
});

// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
  // all loaded
}).orIfSomeFailedCallThis(function() {
  // one or more failed
});

這就是 Promise。如果 HTML 圖片元素有一個 ready() 方法的話,我們就可以這樣:

img1.ready().then(function() {
  // loaded
}, function() {
  // failed
});

// and…
Promise.all([img1.ready(), img2.ready()]).then(function() {
  // all loaded
}, function() {
  // one or more failed
});

基本上 Promise 還是有點像事件回調的,除了:

一個 Promise 只能成功或失敗一次,并且狀態無法改變(不能從成功變為失敗,反之亦然)

如果一個 Promise 成功或者失敗之后,你為其添加針對成功/失敗的回調,則相應的回調函數會立即執行

這些特性非常適合處理異步操作的成功/失敗情景,你無需再擔心事件發生的時間點,而只需對其做出響應。

Promise 相關術語

Domenic Denicola 審閱了本文初稿,給我在術語方面打了個“F”,關了禁閉并且責令我打印 States and Fates 一百遍,還寫了一封家長信給我父母。即便如此,我還是對術語有些迷糊,不過基本上應該是這樣:

一個 Promise 的狀態可以是:

確認(fulfilled)- 成功了
否定(rejected)- 失敗了
等待(pending)- 還沒有確認或者否定,進行中
結束(settled)- 已經確認或者否定了

規范里還使用“thenable”來描述一個對象是否是“類 Promise”(擁有名為“then”的方法)的。這個術語使我想起來前英格蘭足球經理 Terry Venables 所以我盡量少用它。

JavaScript 有了 Promise!

其實已經有一些第三方庫實現了 Promise:

Q

when

WinJS

RSVP.js

上面這些庫和 JavaScript 原生 Promise 都遵守一個通用的、標準化的規范:Promises/A+,jQuery 有個類似的方法叫 Deferreds,但不兼容 Promises/A+ 規范,于是會有點小問題,使用需謹慎。jQuery 還有一個Promise 類型,但只是 Deferreds 的縮減版,所以也有同樣問題。

盡管 Promise 的各路實現遵循同一規范,它們的 API 還是各不相同。JavaScript Promise 的 API 比較接近 RSVP.js,如下創建 Promise:

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

Promise 的構造器接受一個函數作為參數,它會傳遞給這個回調函數兩個變量 resolve 和 reject。在回調函數中做一些異步操作,成功之后調用 resolve,否則調用 reject。

調用 reject 的時候傳遞給它一個 Error 對象只是個慣例并非必須,這和經典 JavaScript 中的 throw 一樣。傳遞 Error 對象的好處是它包含了調用堆棧,在調試的時候會有點用處。

現在來看看如何使用 Promise:

promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});

then 接受兩個參數,成功的時候調用一個,失敗的時候調用另一個,兩個都是可選的,所以你可以只處理成功的情況或者失敗的情況。

JavaScript Promise 最初以“Futures”的名稱歸為 DOM 規范,后來改名為“Promises”,最終納入 JavaScript 規范。將其加入 JavaScript 而非 DOM 的好處是方便在非瀏覽器環境中使用,如Node.js(他們會不會在核心API中使用就是另一回事了)。

瀏覽器支持和 Polyfill

目前的瀏覽器已經(部分)實現了 Promise。

用 Chrome 的話,就像個 Chroman 一樣裝上 Canary 版,默認即啟用了 Promise 支持。如果是 Firefox 擁躉,安裝最新的 nightly build 也一樣。

不過這兩個瀏覽器的實現都還不夠完整徹底,你可以在 bugzilla 上跟蹤 Firefox 的最新進展或者到 Chromium Dashboard 查看 Chrome 的實現情況。

要在這兩個瀏覽器上達到兼容標準 Promise,或者在其他瀏覽器以及 Node.js 中使用 Promise,可以看看這個 polyfill(gzip 之后 2K)

與其他庫的兼容性

JavaScript Promise 的 API 會把任何包含有 then 方法的對象當作“類 Promise”(或者用術語來說就是 thenable。嘆氣)的對象,這些對象經過 Promise.cast() 處理之后就和原生的 JavaScript Promise 實例沒有任何區別了。所以如果你使用的庫返回一個 Q Promise,那沒問題,無縫融入新的 JavaScript Promise。

盡管,如前所述,jQuery 的 Deferred 對象有點……沒什么用,不過幸好還可以轉換成標準 Promise,你最好一拿到對象就馬上加以轉換:

var jsPromise = Promise.cast($.ajax("/whatever.json"));

這里 jQuery 的 $.ajax 返回一個 Deferred 對象,含有 then 方法,因此 Promise.cast 可以將其轉換為 JavaScript Promise。不過有時候 Deferred 對象會給它的回調函數傳遞多個參數,例如:

var jqDeferred = $.ajax("/whatever.json");

jqDeferred.then(function(response, statusText, xhrObj) {
  // ...
}, function(xhrObj, textStatus, err) {
  // ...
});

除了第一個參數,其他都會被 JavaScript Promise 忽略掉:

jsPromise.then(function(response) {
  // ...
}, function(xhrObj) {
  // ...
});

……還好這通常就是你想要的了,至少你能夠用這個辦法實現想要的。另外還要注意,jQuery 也沒有遵循給否定回調函數傳遞 Error 對象的慣例。

復雜的異步代碼變得更簡單了

OK,現在我們來寫點實際的代碼。假設我們想要:

顯示一個加載指示圖標

加載一篇小說的 JSON,包含小說名和每一章內容的 URL。

在頁面中填上小說名

加載所有章節正文

在頁面中添加章節正文

停止加載指示

……這個過程中如果發生什么錯誤了要通知用戶,并且把加載指示停掉,不然它就會不停轉下去,令人眼暈,或者搞壞界面什么的。

當然了,你不會用 JavaScript 去這么繁瑣地顯示一篇文章,直接輸出 HTML 要快得多,不過這個流程是非常典型的 API 請求模式:獲取多個數據,當它們全部完成之后再做一些事情。

首先搞定從網絡加載數據的步驟:

將 Promise 用于 XMLHttpRequest

只要能保持向后兼容,現有 API 都會更新以支持 Promise,XMLHttpRequest 是重點考慮對象之一。不過現在我們先來寫個 GET 請求:

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open("GET", url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

然后調用它:

get("story.json").then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

點擊這里查看代碼運行頁面,打開控制臺查看輸出結果。現在我們可以直接發起 HTTP 請求而不需要手敲 XMLHttpRequest,這樣感覺好多了,能少看一次這個狂駝峰命名的 XMLHttpRequest 我就多快樂一點。

鏈式調用

“then”的故事還沒完,你可以把這些“then”串聯起來修改結果或者添加進行更多異步操作。

值的處理

你可以對結果做些修改然后返回一個新值:

var promise = new Promise(function(resolve, reject) {
  resolve(1);
});

promise.then(function(val) {
  console.log(val); // 1
  return val + 2;
}).then(function(val) {
  console.log(val); // 3
});

回到前面的代碼:

get("story.json").then(function(response) {
  console.log("Success!", response);
});

收到的響應是一個純文本的 JSON,我們可以修改 get 函數,設置 responseType 要求服務器以 JSON 格式提供響應,不過還是用 Promise 的方式來搞定吧:

get("story.json").then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
});

既然 JSON.parse 只接收一個參數,并返回轉換后的結果,我們還可以再精簡一點:

get("story.json").then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
});

點擊這里查看代碼運行頁面,打開控制臺查看輸出結果。事實上,我們可以把 getJSON 函數寫得超級簡單:

function getJSON(url) {
  return get(url).then(JSON.parse);
}

getJSON 會返回一個獲取 JSON 并加以解析的 Promise。

隊列的異步操作

你也可以把 then 串聯起來依次執行異步操作。

當你從 then 的回調函數返回的時候,這里有點小魔法。如果你返回一個值,它就會被傳給下一個 then 的回調;而如果你返回一個“類 Promise”的對象,則下一個 then 就會等待這個 Promise 明確結束(成功/失敗)才會執行。例如:

getJSON("story.json").then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
});

這里我們發起一個對 story.json 的異步請求,返回給我們更多 URL,然后我們會請求其中的第一個。Promise 開始首次顯現出相較事件回調的優越性了。你甚至可以寫一個抓取章節內容的獨立函數:

var storyPromise;

function getChapter(i) {
  storyPromise = storyPromise || getJSON("story.json");

  return storyPromise.then(function(story) {
    return getJSON(story.chapterUrls[i]);
  })
}

// and using it is simple:
getChapter(0).then(function(chapter) {
  console.log(chapter);
  return getChapter(1);
}).then(function(chapter) {
  console.log(chapter);
});

我們一開始并不加載 story.json,直到第一次 getChapter,而以后每次 getChapter 的時候都可以重用已經加載完成的 story Promise,所以 story.json 只需要請求一次。Promise 好棒!

錯誤處理

前面已經看到,“then”接受兩個參數,一個處理成功,一個處理失敗(或者說確認和否定,按 Promise 術語):

get("story.json").then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.log("Failed!", error);
});

你還可以使用 catch

get("story.json").then(function(response) {
  console.log("Success!", response);
}).catch(function(error) {
  console.log("Failed!", error);
});

這里的 catch 并無任何特殊之處,只是 then(undefined, func) 的語法糖衣,更直觀一點而已。注意上面兩段代碼的行為不僅相同,后者相當于:

get("story.json").then(function(response) {
  console.log("Success!", response);
}).then(undefined, function(error) {
  console.log("Failed!", error);
});

差異不大,但意義非凡。Promise 被否定之后會跳轉到之后第一個配置了否定回調的 then(或 catch,一樣的)。對于 then(func1, func2) 來說,必會調用 func1 或 func2 之一,但絕不會兩個都調用。而 then(func1).catch(func2) 這樣,如果 func1 返回否定的話 func2 也會被調用,因為他們是鏈式調用中獨立的兩個步驟。看下面這段代碼:

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don"t worry about it");
}).then(function() {
  console.log("All done!");
});

這段流程非常像 JavaScript 的 try/catch 組合,try 代碼塊中發生的錯誤會徑直跳轉到 catch 代碼塊。這是上面那段代碼的流程圖(我最愛流程圖了):

綠線是確認的 Promise 流程,紅線是否定的。

JavaScript 異常和 Promise

Promise 的否定回調可以由 Promise.reject() 觸發,也可以由構造器回調中拋出的錯誤觸發:

var jsonPromise = new Promise(function(resolve, reject) {
  // JSON.parse throws an error if you feed it some
  // invalid JSON, so this implicitly rejects:
  resolve(JSON.parse("This ain"t JSON"));
});

jsonPromise.then(function(data) {
  // This never happens:
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
});

這意味著你可以把所有 Promise 相關工作都放在構造函數的回調中進行,這樣任何錯誤都能被捕捉到并且觸發 Promise 否定。

get("/").then(JSON.parse).then(function() {
  // This never happens, "/" is an HTML page, not JSON
  // so JSON.parse throws
  console.log("It worked!", data);
}).catch(function(err) {
  // Instead, this happens:
  console.log("It failed!", err);
});
實踐錯誤處理

回到我們的故事和章節,我們用 catch 來捕捉錯誤并顯示給用戶:

getJSON("story.json").then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  addHtmlToPage(chapter1.html);
}).catch(function() {
  addTextToPage("Failed to show chapter");
}).then(function() {
  document.querySelector(".spinner").style.display = "none";
});

如果請求 story.chapterUrls[0] 失敗(http 500 或者用戶掉線什么的)了,它會跳過之后所有針對成功的回調,包括 getJSON 中將響應解析為 JSON 的回調,和這里把第一張內容添加到頁面里的回調。JavaScript 的執行會進入 catch 回調。結果就是前面任何章節請求出錯,頁面上都會顯示“Failed to show chapter”。

和 JavaScript 的 catch 一樣,捕捉到錯誤之后,接下來的代碼會繼續執行,按計劃把加載指示器給停掉。上面的代碼就是下面這段的非阻塞異步版:

try {
  var story = getJSONSync("story.json");
  var chapter1 = getJSONSync(story.chapterUrls[0]);
  addHtmlToPage(chapter1.html);
}
catch (e) {
  addTextToPage("Failed to show chapter");
}

document.querySelector(".spinner").style.display = "none";

如果只是要捕捉異常做記錄輸出,不打算在用戶界面上對錯誤進行反饋的話,只要拋出 Error 就行了,這一步可以放在 getJSON 中:

function getJSON(url) {
  return get(url).then(JSON.parse).catch(function(err) {
    console.log("getJSON failed for", url, err);
    throw err;
  });
}

現在我們已經搞定第一章了,接下來搞定所有的。

并行和串行 —— 魚與熊掌兼得

異步的思維方式并不符合直覺,如果你覺得起步困難,那就試試先寫個同步的方法,就像這個:

try {
  var story = getJSONSync("story.json");
  addHtmlToPage(story.heading);

  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });

  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}

document.querySelector(".spinner").style.display = "none";

它執行起來完全正常!(查看示例)不過它是同步的,在加載內容時會卡住整個瀏覽器。要讓它異步工作的話,我們用 then 把它們一個接一個串起來:

getJSON("story.json").then(function(story) {
  addHtmlToPage(story.heading);

  // TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
  // And we"re all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector(".spinner").style.display = "none";
});

那么我們如何遍歷章節的 URL 并且依次請求?這樣是不行的

story.chapterUrls.forEach(function(chapterUrl) {
  // Fetch chapter
  getJSON(chapterUrl).then(function(chapter) {
    // and add it to the page
    addHtmlToPage(chapter.html);
  });
});

forEach 沒有對異步操作的支持,所以我們的故事章節會按照它們加載完成的順序顯示,基本上《低俗小說》就是這么寫出來的。我們不寫低俗小說,所以得修正它:

創建序列

我們要把章節 URL 數組轉換成 Promise 的序列,還是用 then:

// Start off with a promise that always resolves
var sequence = Promise.resolve();

// Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
  // Add these actions to the end of the sequence
  sequence = sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
});

這是我們第一次用到 Promise.resolve,它會依據你傳的任何值返回一個 Promise。如果你傳給它一個類 Promise 對象(帶有 then 方法),它會生成一個帶有同樣確認/否定回調的 Promise,基本上就是克隆。如果傳給它任何別的值,如 Promise.resolve("Hello"),它會創建一個以這個值為完成結果的 Promise,如果不傳任何值,則以 undefined 為完成結果。

還有一個對應的 Promise.reject(val),會創建以你傳入的參數(或 undefined)為否定結果的 Promise。

我們可以用 array.reduce 精簡一下上面的代碼:

// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
  // Add these actions to the end of the sequence
  return sequence.then(function() {
    return getJSON(chapterUrl);
  }).then(function(chapter) {
    addHtmlToPage(chapter.html);
  });
}, Promise.resolve());

它和前面的例子功能一樣,但是不需要顯式聲明 sequence 變量。reduce 回調會依次應用在每個數組元素上,第一輪里的“sequence”是 Promise.resolve(),之后的調用里“sequence”就是上次函數執行的的結果。array.reduce 非常適合用于把一組值歸并處理為一個值,正是我們現在對 Promise 的用法。

匯總下上面的代碼:

getJSON("story.json").then(function(story) {
  addHtmlToPage(story.heading);

  return story.chapterUrls.reduce(function(sequence, chapterUrl) {
    // Once the last chapter"s promise is done…
    return sequence.then(function() {
      // …fetch the next chapter
      return getJSON(chapterUrl);
    }).then(function(chapter) {
      // and add it to the page
      addHtmlToPage(chapter.html);
    });
  }, Promise.resolve());
}).then(function() {
  // And we"re all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector(".spinner").style.display = "none";
});

運行示例看這里,前面的同步代碼改造成了完全異步的版本。我們還可以更進一步,現在頁面加載的效果是這樣:

瀏覽器很擅長同時加載多個文件,我們這種一個接一個下載章節的方法非常不效率。我們希望同時下載所有章節,全部完成后一次搞定,正好就有這么個 API:

Promise.all(arrayOfPromises).then(function(arrayOfResults) {
  //...
});

Promise.all 接受一個 Promise 數組為參數,創建一個當所有 Promise 都完成之后就完成的 Promise,它的完成結果是一個數組,包含了所有先前傳入的那些 Promise 的完成結果,順序和將它們傳入的數組順序一致。

getJSON("story.json").then(function(story) {
  addHtmlToPage(story.heading);

  // Take an array of promises and wait on them all
  return Promise.all(
    // Map our array of chapter urls to
    // an array of chapter json promises
    story.chapterUrls.map(getJSON)
  );
}).then(function(chapters) {
  // Now we have the chapters jsons in order! Loop through…
  chapters.forEach(function(chapter) {
    // …and add to the page
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened so far
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector(".spinner").style.display = "none";
});

根據連接狀況,改進的代碼會比順序加載方式提速數秒,甚至代碼行數也更少。章節加載完成的順序不確定,但它們顯示在頁面上的順序準確無誤。

然而這樣還是有提高空間。當第一章內容加載完畢我們可以立即填進頁面,這樣用戶可以在其他加載任務尚未完成之前就開始閱讀;當第三章到達的時候我們不動聲色,第二章也到達之后我們再把第二章和第三章內容填入頁面,以此類推。

為了達到這樣的效果,我們同時請求所有的章節內容,然后創建一個序列依次將其填入頁面:

getJSON("story.json").then(function(story) {
  addHtmlToPage(story.heading);

  // Map our array of chapter urls to
  // an array of chapter json promises.
  // This makes sure they all download parallel.
  return story.chapterUrls.map(getJSON)
    .reduce(function(sequence, chapterPromise) {
      // Use reduce to chain the promises together,
      // adding content to the page for each chapter
      return sequence.then(function() {
        // Wait for everything in the sequence so far,
        // then wait for this chapter to arrive.
        return chapterPromise;
      }).then(function(chapter) {
        addHtmlToPage(chapter.html);
      });
    }, Promise.resolve());
}).then(function() {
  addTextToPage("All done");
}).catch(function(err) {
  // catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector(".spinner").style.display = "none";
});

哈哈(查看示例),魚與熊掌兼得!加載所有內容的時間未變,但用戶可以更早看到第一章。

這個小例子中各部分章節加載差不多同時完成,逐章顯示的策略在章節內容很多的時候優勢將會更加顯著。

上面的代碼如果用 Node.js 風格的回調或者事件機制實現的話代碼量大約要翻一倍,更重要的是可讀性也不如此例。然而,Promise 的厲害不止于此,和其他 ES6 的新功能結合起來還能更加高效……

附贈章節:Promise 和 Generator

接下來的內容涉及到一大堆 ES6 的新特性,不過對于現在應用 Promise 來說并非必須,把它當作接下來的第二部豪華續集的預告片來看就好了。

ES6 還給我們帶來了 Generator,允許函數在特定地方像 return 一樣退出,但是稍后又能恢復到這個位置和狀態上繼續執行。

function *addGenerator() {
  var i = 0;
  while (true) {
    i += yield i;
  }
}

注意函數名前的星號,這表示該函數是一個 Generator。關鍵字 yield 標記了暫停/繼續的位置,使用方法像這樣:

var adder = addGenerator();
adder.next().value; // 0
adder.next(5).value; // 5
adder.next(5).value; // 10
adder.next(5).value; // 15
adder.next(50).value; // 65

這對 Promise 有什么用呢?你可以用這種暫停/繼續的機制寫出來和同步代碼看上去差不多(理解起來也一樣簡單)的代碼。下面是一個輔助函數(helper function),我們在 yield 位置等待 Promise 完成:

function spawn(generatorFunc) {
  function continuer(verb, arg) {
    var result;
    try {
      result = generator[verb](arg);
    } catch (err) {
      return Promise.reject(err);
    }
    if (result.done) {
      return result.value;
    } else {
      return Promise.cast(result.value).then(onFulfilled, onRejected);
    }
  }
  var generator = generatorFunc();
  var onFulfilled = continuer.bind(continuer, "next");
  var onRejected = continuer.bind(continuer, "throw");
  return onFulfilled();
}

這段代碼原樣拷貝自 Q,只是改成 JavaScript Promise 的 API。把我們前面的最終方案和 ES6 最新特性結合在一起之后:

spawn(function *() {
  try {
    // "yield" effectively does an async wait,
    // returning the result of the promise
    let story = yield getJSON("story.json");
    addHtmlToPage(story.heading);

    // Map our array of chapter urls to
    // an array of chapter json promises.
    // This makes sure they all download parallel.
    let chapterPromises = story.chapterUrls.map(getJSON);

    for (let chapterPromise of chapterPromises) {
      // Wait for each chapter to be ready, then add it to the page
      let chapter = yield chapterPromise;
      addHtmlToPage(chapter.html);
    }

    addTextToPage("All done");
  }
  catch (err) {
    // try/catch just works, rejected promises are thrown here
    addTextToPage("Argh, broken: " + err.message);
  }
  document.querySelector(".spinner").style.display = "none";
});

功能完全一樣,讀起來要簡單得多。這個例子目前可以在 Chrome Canary 中運行(查看示例),不過你得先到 about:flags 中開啟 Enable experimental JavaScript 選項。

這里用到了一堆 ES6 的新語法:Promise、Generator、let、for-of。當我們把 yield 應用在一個 Promise 上,spawn 輔助函數會等待 Promise 完成,然后才返回最終的值。如果 Promise 給出否定結果,spawn 中的 yield 則會拋出一個異常,我們可以用 try/catch 捕捉到。這樣寫異步代碼真是超級簡單!

Promise API 參考

除非額外注明,最新版的 Chrome(Canary) 和 Firefox(nightly) 均支持下列所有方法。這個 Polyfill 則在所有瀏覽器內實現同樣的接口。

靜態方法

Promise.cast(promise);
返回一個 Promise(當且僅當 promise.constructor == Promise)
備注:目前僅有 Chrome 實現

Promise.cast(obj);
創建一個以 obj 為成功結果的 Promise。
備注:目前僅有 Chrome 實現

Promise.resolve(thenable);
從 thenable 對象創建一個新的 Promise。一個 thenable(類 Promise)對象是一個帶有“then”方法的對象。如果你傳入一個原生的 JavaScript Promise 對象,則會創建一個新的 Promise。此方法涵蓋了 Promise.cast 的特性,但是不如 Promise.cast 更簡單高效。

Promise.resolve(obj);
創建一個以 obj 為確認結果的 Promise。這種情況下等同于 Promise.cast(obj)。

Promise.reject(obj);
創建一個以 obj 為否定結果的 Promise。為了一致性和調試便利(如堆棧追蹤),obj 應該是一個 Error 實例對象。

Promise.all(array);
創建一個 Promise,當且僅當傳入數組中的所有 Promise 都確認之后才確認,如果遇到數組中的任何一個 Promise 以否定結束,則拋出否定結果。每個數組元素都會首先經過 Promise.cast,所以數組可以包含類 Promise 對象或者其他對象。確認結果是一個數組,包含傳入數組中每個 Promise 的確認結果(且保持順序);否定結果是傳入數組中第一個遇到的否定結果。
備注:目前僅有 Chrome 實現

Promise.race(array);
創建一個 Promise,當數組中的任意對象確認時將其結果作為確認結束,或者當數組中任意對象否定時將其結果作為否定結束。
備注:我不大確定這個接口是否有用,我更傾向于一個 Promise.all 的對立方法,僅當所有數組元素全部給出否定的時候才拋出否定結果

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77997.html

相關文章

  • 值得收藏 7 個有用JavaScript技巧

    摘要:值得收藏個有用技巧像其它語言一樣中也可以通過一些技巧來完成一些復雜的操作接下來我們學習吧數組去重數組和布爾有時我們需要過濾數組中值為的值例如你可能不知道這樣的技巧是不是很簡單只需要傳入一個函數即可創建一個空對象有時我們需要創建一個純凈的對象 值得收藏 7 個有用JavaScript技巧 像其它語言一樣,JavaScript中也可以通過一些技巧來完成一些復雜的操作. 接下來我們學習吧 數...

    layman 評論0 收藏0
  • es6 7個比較有用的技巧

    摘要:數組去重數組和布爾有時我們需要過濾數組中值為的值例如你可能不知道這樣的技巧是不是很簡單只需要傳入一個函數即可創建一個空對象有時我們需要創建一個純凈的對象不包含什么原型鏈等等一般創建空對象最直接方式通過字面量但這個對象中依然存在屬性來指向等等 數組去重 var arr = [1, 2, 3, 3, 4]; console.log(...new Set(arr)) >> [1, 2, 3,...

    Apollo 評論0 收藏0
  • es6 7個比較有用的技巧

    摘要:數組去重數組和布爾有時我們需要過濾數組中值為的值例如你可能不知道這樣的技巧是不是很簡單只需要傳入一個函數即可創建一個空對象有時我們需要創建一個純凈的對象不包含什么原型鏈等等一般創建空對象最直接方式通過字面量但這個對象中依然存在屬性來指向等等 數組去重 var arr = [1, 2, 3, 3, 4]; console.log(...new Set(arr)) >> [1, 2, 3,...

    junbaor 評論0 收藏0
  • React 簡介

    摘要:介紹是內部的一個類庫并開源,可用于創建用戶交互界面。主要有四個主要概念構成,下面分別來簡單介紹一下之所以引入虛擬,一方面是性能的考慮。允許直接在模板插入變量。這種單向數據流使得整個系統都是透明可預測的。 React 介紹 React是Facrbook內部的一個JavaScript類庫并開源,可用于創建Web用戶交互界面。它引入了一種新的方式來處理瀏覽器DOM。那些需要手動更新DOM、費...

    voidking 評論0 收藏0
  • FCC 成都社區·前端周刊 第 11 期

    摘要:正式發布已正式發布,新版本重點關注工具鏈以及工具鏈在中的運行速度問題。文章內容包括什么是內存,內存生命周期,中的內存分配,內存釋放,垃圾收集,種常見的內存泄漏以及如何處理內存泄漏的技巧。 1. Angular 6 正式發布 Angular 6.0.0 已正式發布,新版本重點關注工具鏈以及工具鏈在 Angular 中的運行速度問題。Angular v6 是統一整體框架、Material ...

    lentrue 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<