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

資訊專欄INFORMATION COLUMN

JavaScript Promise:去而復(fù)返

Lowky / 2342人閱讀

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

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

女士們先生們,請準(zhǔn)備好迎接 Web 開發(fā)歷史上一個(gè)重大時(shí)刻……

[鼓聲響起]

  

JavaScript 有了原生的 Promise!

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

這時(shí)候你大概是這三種人之一:

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

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

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

他們都在激動(dòng)什么?

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

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

你應(yīng)該會用事件加回調(diào)的辦法來處理這類情況:

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

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

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

這樣就不打噴嚏了。我們添加幾個(gè)監(jiān)聽函數(shù),請求圖片,然后 JavaScript 就停止運(yùn)行了,直到觸發(fā)某個(gè)監(jiān)聽函數(shù)。

上面的例子中唯一的問題是,事件有可能在我們綁定監(jiān)聽器之前就已經(jīng)發(fā)生,所以我們先要檢查圖片的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
});

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

事件不是萬金油

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

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

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

這就是 Promise。如果 HTML 圖片元素有一個(gè) 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 還是有點(diǎn)像事件回調(diào)的,除了:

一個(gè) Promise 只能成功或失敗一次,并且狀態(tài)無法改變(不能從成功變?yōu)槭。粗嗳唬?/p>

如果一個(gè) Promise 成功或者失敗之后,你為其添加針對成功/失敗的回調(diào),則相應(yīng)的回調(diào)函數(shù)會立即執(zhí)行

這些特性非常適合處理異步操作的成功/失敗情景,你無需再擔(dān)心事件發(fā)生的時(shí)間點(diǎn),而只需對其做出響應(yīng)。

Promise 相關(guān)術(shù)語

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

一個(gè) Promise 的狀態(tài)可以是:

確認(rèn)(fulfilled)- 成功了
否定(rejected)- 失敗了
等待(pending)- 還沒有確認(rèn)或者否定,進(jìn)行中
結(jié)束(settled)- 已經(jīng)確認(rèn)或者否定了

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

JavaScript 有了 Promise!

其實(shí)已經(jīng)有一些第三方庫實(shí)現(xiàn)了 Promise:

Q

when

WinJS

RSVP.js

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

盡管 Promise 的各路實(shí)現(xiàn)遵循同一規(guī)范,它們的 API 還是各不相同。JavaScript Promise 的 API 比較接近 RSVP.js,如下創(chuàng)建 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 的構(gòu)造器接受一個(gè)函數(shù)作為參數(shù),它會傳遞給這個(gè)回調(diào)函數(shù)兩個(gè)變量 resolve 和 reject。在回調(diào)函數(shù)中做一些異步操作,成功之后調(diào)用 resolve,否則調(diào)用 reject。

調(diào)用 reject 的時(shí)候傳遞給它一個(gè) Error 對象只是個(gè)慣例并非必須,這和經(jīng)典 JavaScript 中的 throw 一樣。傳遞 Error 對象的好處是它包含了調(diào)用堆棧,在調(diào)試的時(shí)候會有點(diǎn)用處。

現(xiàn)在來看看如何使用 Promise:

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

then 接受兩個(gè)參數(shù),成功的時(shí)候調(diào)用一個(gè),失敗的時(shí)候調(diào)用另一個(gè),兩個(gè)都是可選的,所以你可以只處理成功的情況或者失敗的情況。

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

瀏覽器支持和 Polyfill

目前的瀏覽器已經(jīng)(部分)實(shí)現(xiàn)了 Promise。

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

不過這兩個(gè)瀏覽器的實(shí)現(xiàn)都還不夠完整徹底,你可以在 bugzilla 上跟蹤 Firefox 的最新進(jìn)展或者到 Chromium Dashboard 查看 Chrome 的實(shí)現(xiàn)情況。

要在這兩個(gè)瀏覽器上達(dá)到兼容標(biāo)準(zhǔn) Promise,或者在其他瀏覽器以及 Node.js 中使用 Promise,可以看看這個(gè) polyfill(gzip 之后 2K)

與其他庫的兼容性

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

盡管,如前所述,jQuery 的 Deferred 對象有點(diǎn)……沒什么用,不過幸好還可以轉(zhuǎn)換成標(biāo)準(zhǔn) Promise,你最好一拿到對象就馬上加以轉(zhuǎn)換:

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

這里 jQuery 的 $.ajax 返回一個(gè) Deferred 對象,含有 then 方法,因此 Promise.cast 可以將其轉(zhuǎn)換為 JavaScript Promise。不過有時(shí)候 Deferred 對象會給它的回調(diào)函數(shù)傳遞多個(gè)參數(shù),例如:

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

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

除了第一個(gè)參數(shù),其他都會被 JavaScript Promise 忽略掉:

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

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

復(fù)雜的異步代碼變得更簡單了

OK,現(xiàn)在我們來寫點(diǎn)實(shí)際的代碼。假設(shè)我們想要:

顯示一個(gè)加載指示圖標(biāo)

加載一篇小說的 JSON,包含小說名和每一章內(nèi)容的 URL。

在頁面中填上小說名

加載所有章節(jié)正文

在頁面中添加章節(jié)正文

停止加載指示

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

當(dāng)然了,你不會用 JavaScript 去這么繁瑣地顯示一篇文章,直接輸出 HTML 要快得多,不過這個(gè)流程是非常典型的 API 請求模式:獲取多個(gè)數(shù)據(jù),當(dāng)它們?nèi)客瓿芍笤僮鲆恍┦虑椤?/p>

首先搞定從網(wǎng)絡(luò)加載數(shù)據(jù)的步驟:

將 Promise 用于 XMLHttpRequest

只要能保持向后兼容,現(xiàn)有 API 都會更新以支持 Promise,XMLHttpRequest 是重點(diǎn)考慮對象之一。不過現(xiàn)在我們先來寫個(gè) 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();
  });
}

然后調(diào)用它:

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

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

鏈?zhǔn)秸{(diào)用

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

值的處理

你可以對結(jié)果做些修改然后返回一個(gè)新值:

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);
});

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

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

既然 JSON.parse 只接收一個(gè)參數(shù),并返回轉(zhuǎn)換后的結(jié)果,我們還可以再精簡一點(diǎn):

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

點(diǎn)擊這里查看代碼運(yùn)行頁面,打開控制臺查看輸出結(jié)果。事實(shí)上,我們可以把 getJSON 函數(shù)寫得超級簡單:

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

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

隊(duì)列的異步操作

你也可以把 then 串聯(lián)起來依次執(zhí)行異步操作。

當(dāng)你從 then 的回調(diào)函數(shù)返回的時(shí)候,這里有點(diǎn)小魔法。如果你返回一個(gè)值,它就會被傳給下一個(gè) then 的回調(diào);而如果你返回一個(gè)“類 Promise”的對象,則下一個(gè) then 就會等待這個(gè) Promise 明確結(jié)束(成功/失敗)才會執(zhí)行。例如:

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

這里我們發(fā)起一個(gè)對 story.json 的異步請求,返回給我們更多 URL,然后我們會請求其中的第一個(gè)。Promise 開始首次顯現(xiàn)出相較事件回調(diào)的優(yōu)越性了。你甚至可以寫一個(gè)抓取章節(jié)內(nèi)容的獨(dú)立函數(shù):

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 的時(shí)候都可以重用已經(jīng)加載完成的 story Promise,所以 story.json 只需要請求一次。Promise 好棒!

錯(cuò)誤處理

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

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) 的語法糖衣,更直觀一點(diǎn)而已。注意上面兩段代碼的行為不僅相同,后者相當(dāng)于:

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

差異不大,但意義非凡。Promise 被否定之后會跳轉(zhuǎn)到之后第一個(gè)配置了否定回調(diào)的 then(或 catch,一樣的)。對于 then(func1, func2) 來說,必會調(diào)用 func1 或 func2 之一,但絕不會兩個(gè)都調(diào)用。而 then(func1).catch(func2) 這樣,如果 func1 返回否定的話 func2 也會被調(diào)用,因?yàn)樗麄兪擎準(zhǔn)秸{(diào)用中獨(dú)立的兩個(gè)步驟。看下面這段代碼:

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 代碼塊中發(fā)生的錯(cuò)誤會徑直跳轉(zhuǎn)到 catch 代碼塊。這是上面那段代碼的流程圖(我最愛流程圖了):

綠線是確認(rèn)的 Promise 流程,紅線是否定的。

JavaScript 異常和 Promise

Promise 的否定回調(diào)可以由 Promise.reject() 觸發(fā),也可以由構(gòu)造器回調(diào)中拋出的錯(cuò)誤觸發(fā):

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 相關(guān)工作都放在構(gòu)造函數(shù)的回調(diào)中進(jìn)行,這樣任何錯(cuò)誤都能被捕捉到并且觸發(fā) 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);
});
實(shí)踐錯(cuò)誤處理

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

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 或者用戶掉線什么的)了,它會跳過之后所有針對成功的回調(diào),包括 getJSON 中將響應(yīng)解析為 JSON 的回調(diào),和這里把第一張內(nèi)容添加到頁面里的回調(diào)。JavaScript 的執(zhí)行會進(jìn)入 catch 回調(diào)。結(jié)果就是前面任何章節(jié)請求出錯(cuò),頁面上都會顯示“Failed to show chapter”。

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

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";

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

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

現(xiàn)在我們已經(jīng)搞定第一章了,接下來搞定所有的。

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

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

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";

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

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";
});

那么我們?nèi)绾伪闅v章節(jié)的 URL 并且依次請求?這樣是不行的

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

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

創(chuàng)建序列

我們要把章節(jié) URL 數(shù)組轉(zhuǎn)換成 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,它會依據(jù)你傳的任何值返回一個(gè) Promise。如果你傳給它一個(gè)類 Promise 對象(帶有 then 方法),它會生成一個(gè)帶有同樣確認(rèn)/否定回調(diào)的 Promise,基本上就是克隆。如果傳給它任何別的值,如 Promise.resolve("Hello"),它會創(chuàng)建一個(gè)以這個(gè)值為完成結(jié)果的 Promise,如果不傳任何值,則以 undefined 為完成結(jié)果。

還有一個(gè)對應(yīng)的 Promise.reject(val),會創(chuàng)建以你傳入的參數(shù)(或 undefined)為否定結(jié)果的 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 回調(diào)會依次應(yīng)用在每個(gè)數(shù)組元素上,第一輪里的“sequence”是 Promise.resolve(),之后的調(diào)用里“sequence”就是上次函數(shù)執(zhí)行的的結(jié)果。array.reduce 非常適合用于把一組值歸并處理為一個(gè)值,正是我們現(xiàn)在對 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";
});

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

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

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

Promise.all 接受一個(gè) Promise 數(shù)組為參數(shù),創(chuàng)建一個(gè)當(dāng)所有 Promise 都完成之后就完成的 Promise,它的完成結(jié)果是一個(gè)數(shù)組,包含了所有先前傳入的那些 Promise 的完成結(jié)果,順序和將它們傳入的數(shù)組順序一致。

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";
});

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

然而這樣還是有提高空間。當(dāng)?shù)谝徽聝?nèi)容加載完畢我們可以立即填進(jìn)頁面,這樣用戶可以在其他加載任務(wù)尚未完成之前就開始閱讀;當(dāng)?shù)谌碌竭_(dá)的時(shí)候我們不動(dòng)聲色,第二章也到達(dá)之后我們再把第二章和第三章內(nèi)容填入頁面,以此類推。

為了達(dá)到這樣的效果,我們同時(shí)請求所有的章節(jié)內(nèi)容,然后創(chuàng)建一個(gè)序列依次將其填入頁面:

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";
});

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

這個(gè)小例子中各部分章節(jié)加載差不多同時(shí)完成,逐章顯示的策略在章節(jié)內(nèi)容很多的時(shí)候優(yōu)勢將會更加顯著。

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

附贈章節(jié):Promise 和 Generator

接下來的內(nèi)容涉及到一大堆 ES6 的新特性,不過對于現(xiàn)在應(yīng)用 Promise 來說并非必須,把它當(dāng)作接下來的第二部豪華續(xù)集的預(yù)告片來看就好了。

ES6 還給我們帶來了 Generator,允許函數(shù)在特定地方像 return 一樣退出,但是稍后又能恢復(fù)到這個(gè)位置和狀態(tài)上繼續(xù)執(zhí)行。

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

注意函數(shù)名前的星號,這表示該函數(shù)是一個(gè) Generator。關(guān)鍵字 yield 標(biāo)記了暫停/繼續(xù)的位置,使用方法像這樣:

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 有什么用呢?你可以用這種暫停/繼續(xù)的機(jī)制寫出來和同步代碼看上去差不多(理解起來也一樣簡單)的代碼。下面是一個(gè)輔助函數(shù)(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 最新特性結(jié)合在一起之后:

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";
});

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

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

Promise API 參考

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

靜態(tài)方法

Promise.cast(promise);
返回一個(gè) Promise(當(dāng)且僅當(dāng) promise.constructor == Promise)
備注:目前僅有 Chrome 實(shí)現(xiàn)

Promise.cast(obj);
創(chuàng)建一個(gè)以 obj 為成功結(jié)果的 Promise。
備注:目前僅有 Chrome 實(shí)現(xiàn)

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

Promise.resolve(obj);
創(chuàng)建一個(gè)以 obj 為確認(rèn)結(jié)果的 Promise。這種情況下等同于 Promise.cast(obj)。

Promise.reject(obj);
創(chuàng)建一個(gè)以 obj 為否定結(jié)果的 Promise。為了一致性和調(diào)試便利(如堆棧追蹤),obj 應(yīng)該是一個(gè) Error 實(shí)例對象。

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

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/77997.html

相關(guān)文章

  • 值得收藏 7 個(gè)有用JavaScript技巧

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

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

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

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

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

    junbaor 評論0 收藏0
  • React 簡介

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

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

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

    lentrue 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<