理解Promise in JavaScript

Promise是JavaScript中的一個核心概念倘感,初學(xué)JavaScript咙咽,對Promise的概念和用法都比較模糊,這里做一個總結(jié)蜡豹。
首先Promise是用來執(zhí)行異步代碼的,用來替代回調(diào)函數(shù)余素。在講回調(diào)函數(shù)之前,可以先看下什么是同步操作威根,什么是異步操作视乐。

同步操作

正常代碼是同步執(zhí)行的,執(zhí)行一條命令需要等它完成之后才會執(zhí)行下一條命令留美。例如讀取一個文件的內(nèi)容伸刃,

// Node.js program to demonstrate the
// fs.readFileSync() method

// Include fs module
const fs = require('fs');

// Calling the readFileSync() method
// to read 'input.txt' file
const data = fs.readFileSync('./input.txt',
            {encoding:'utf8', flag:'r'});

// Display the file data
console.log(data);

執(zhí)行完之后立刻就可以知道文件的內(nèi)容,但這種方式耗時比較久景图,在等待執(zhí)行完成的過程時候碉哑,不能執(zhí)行其他操作,這樣就很容易阻塞到UI線程妆毕。

異步操作

與同步方式不同贮尖,異步操作在調(diào)用某個函數(shù)后立刻返回笛粘,同時使用callback函數(shù)湿硝,在異步操作完成的時候自動調(diào)用。讀文件用異步來操作可以表示為:

readFile("./input.txt", (error, result) => {
  // This callback will be called when the task is done, with the
  // final `error` or `result`. Any operation dependent on the
  // result must be defined within this callback.
  console.log(result);
});
// Code here is immediately executed after the `readFile` request
// is fired. It does not wait for the callback to be called, hence
// making `readFile` "asynchronous".

這種異步操作有兩個缺點序六,一個是多層的callback會比較晦澀難懂蚤吹,不容易維護;第二個是出錯處理很麻煩繁涂,因為實際在執(zhí)行回調(diào)函數(shù)的時候已經(jīng)沒有之前同步執(zhí)行的堆棧,沒有辦法回溯到可以處理異常的代碼塊秉沼。

Promise

Promise用來表示一次異步操作的未來結(jié)果矿酵,既然是表示未來的結(jié)果,那就需要通過這個Promise可以知道異步操作是否成功敞咧,如果成功辜腺,返回值是什么?如果失敗测砂,失敗的異常是什么百匆。同時因為是異步操作,Promise可以注冊回調(diào)函數(shù)胧华,需要注意的是矩动,注冊的回調(diào)函數(shù)最多只能被調(diào)用一次释漆;

創(chuàng)建Promise

  1. 使用Promise的構(gòu)造函數(shù)來創(chuàng)建,用法如下:
    創(chuàng)建一個Promise的語法是
new Promise(executor)

其中executor是一個函數(shù)示姿,接受兩個參數(shù)resolvereject逊笆。resolvereject都是函數(shù),并且接受一個任意類型的輸入?yún)?shù)子檀。
其中resolve函數(shù)解決或兌現(xiàn)返回的Promise,或者調(diào)用reject函數(shù)拒絕返回的Promise.
上面這個例子中的執(zhí)行過程如下:

  1. new Promise會構(gòu)造一個Promise對象褂痰,同時會產(chǎn)生兩個函數(shù)對象缩歪,分別是resolvereject,這兩個函數(shù)對象和這個Promise對象綁定在一起匪蝙。
  2. new Promise的參數(shù)是一個函數(shù)executor逛球,這個函數(shù)用來封裝一些操作,這些操作會通過異步的方式執(zhí)行需忿。同時這個函數(shù)接受兩參數(shù)屋厘,分別是resolvereject. executor在創(chuàng)建Promise后立刻執(zhí)行,同時把resolvereject的對象作為參數(shù)汗洒。
  3. 這個Promise的狀態(tài)是通過調(diào)用resolvereject的調(diào)用來改變的溢谤。
  • 如果resolve先被調(diào)用,那么Promise就被解決或者兌現(xiàn)世杀,同時傳給resovle的參數(shù)會被認為Promise對象對應(yīng)的結(jié)果瞻坝。
  • 如果reject先被調(diào)用,那么Promise則被拒絕所刀,同時傳給reject的參數(shù)也會被認為是Promise對象對應(yīng)的異常浮创。
    以下例子來源于MDN:
const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code.
  // In reality, you will probably be using something like XHR or an HTML API.
  setTimeout(() => {
    resolve("Success!"); // Yay! Everything went well!
  }, 250);
});

myFirstPromise.then((successMessage) => {
  // successMessage is whatever we passed in the resolve(...) function above.
  // It doesn't have to be a string, but if it is only a succeed message, it probably will be.
  console.log(`Yay! ${successMessage}`);
});

以上例子中就是通過resolve來把"Success"信息傳給通過myFirstPromise.then注冊的回調(diào)函數(shù),同時myFirstPromise也會變?yōu)?code>fulfill的狀態(tài)溜族。

  1. 使用then()函數(shù)來創(chuàng)建
    then()可以創(chuàng)建并返回一個新的Promise,例如
function getJSON(url){
  return fetch(url).then(response => response.json());
}

fetch(url)返回是一個Promise P1劣像,P1對應(yīng)的實際結(jié)果是一個Response對象摧玫。Response對象的 json()方法返回一個新的Promise P2,那么P1被解決為P2屋群。當(dāng)P2兌現(xiàn)時(fulfill)坏挠,P1也會用相同的值來fulfill.
還是用剛才讀文件的例子來說明,如果用Promise實現(xiàn)的話就是以下方式:

const readFilePromise = (path) =>
  new Promise((resolve, reject) => {
    readFile(path, (error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });

readFilePromise("./input.txt")
  .then((result) => console.log(result))
  .catch((error) => console.error("Failed to read data"));

使用Promise

如果使用Promise異步对竣,假設(shè)有一個函數(shù)readFilePromise榜配,那么上面的代碼就變成:

readFilePromise("./input.txt").then(fileContent => {
  // 該函數(shù)為callback函數(shù),在文件內(nèi)容被讀出的時候調(diào)用临燃,接受的參數(shù)為
  // 文件的內(nèi)容烙心。注意該函數(shù)只會被調(diào)用一次。
  console.log(fileContent);
});

注意readFilePromise返回的是一個Promise對象爪瓜,Promise對象有then()的方法痘昌,可以用來注冊回調(diào)函數(shù)炬转。該回調(diào)函數(shù)在Promise兌現(xiàn)后調(diào)用,調(diào)用的時候傳入的參數(shù)為該Promise的實際結(jié)果驻啤,在此處就是文件的實際內(nèi)容荐吵;
如果讀取文件失敗赊瞬,怎么處理:

readFilePromise("./input.txt").then(fileContent => {
  // 該函數(shù)為callback函數(shù)巧涧,在文件內(nèi)容被讀出的時候調(diào)用遥倦,接受的參數(shù)為
  // 文件的內(nèi)容。注意該函數(shù)只會被調(diào)用一次缩筛。
  console.log(fileContent);
}).catch(() => console.log("error"));

如果要等待Promise的完成堡称,可以使用

await yourPromise;

Promise的狀態(tài)

一個Promise可以有三個狀態(tài),分別是fulfill却紧,rejectpending晓殊。剛創(chuàng)立的時候,Promise的狀態(tài)為pending挺物,一旦被fulfill或者reject识藤,則該Promise為settle,且狀態(tài)不會改變痴昧。
一個Promise代表了異步操作的結(jié)果,如果異步代碼正常結(jié)束舌镶,這個結(jié)果就是代碼的正常返回值豪娜,同時這個結(jié)果會作為參數(shù)傳遞給then()的第一個參數(shù)注冊的函數(shù);如果代碼執(zhí)行異常否灾,那這個結(jié)果就是一個Error對象或者某個其他值鸣奔,這個結(jié)果會作為參數(shù)傳遞給catch()注冊的或者then()的第二個參數(shù)注冊的回調(diào)函數(shù)。

Promise Chain

先看一個例子:

fetch(theURL)                // task 1, return Promise P1
    .then(callback1)         // task 2, return Promise P2
    .then(callback2);        // task 3, return Promise P3

callback1是當(dāng)P1兌現(xiàn)的時候調(diào)用扣汪,并且把P1的結(jié)果作為參數(shù)傳給callback1; callback1必須返回一個新的Promise P2崭别,并把P2兌現(xiàn)的結(jié)果作為輸入?yún)?shù)送給callback2
舉一個具體的例子:

function callback1(response){
  let p4 = response.json();
  return p4
}

function callback2(profile){
  displayUserProfile(profile);
}
let p1 = fetch("/api/user/profile");
let p2 = p1.then(callback1);
let p3 = p2.then(callback2);

Reference

MDN Promise
參數(shù)結(jié)構(gòu)
JavaScript權(quán)威指南第7版

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末账千,一起剝皮案震驚了整個濱河市暗膜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娃善,老刑警劉巖瑞佩,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炬丸,死亡現(xiàn)場離奇詭異,居然都是意外死亡稠炬,警方通過查閱死者的電腦和手機首启,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褒纲,“玉大人钥飞,你說我怎么就攤上這事≈” “怎么了论悴?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵膀估,是天一觀的道長。 經(jīng)常有香客問我察纯,道長,這世上最難降的妖魔是什么香伴? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任即纲,我火速辦了婚禮博肋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膊畴。我一直安慰自己病游,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布轻绞。 她就那樣靜靜地躺著佣耐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奸远。 梳的紋絲不亂的頭發(fā)上讽挟,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天耽梅,我揣著相機與錄音,去河邊找鬼。 笑死佩番,一個胖子當(dāng)著我的面吹牛罢杉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赋秀,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼著洼,長吁一口氣:“原來是場噩夢啊……” “哼澈歉!你這毒婦竟也來了涡尘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤贫途,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后那先,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年说贝,在試婚紗的時候發(fā)現(xiàn)自己被綠了傲宜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撇眯。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡沪蓬,死狀恐怖来候,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤梆砸,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布园欣,位于F島的核電站沸枯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绑榴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一窃诉、第九天 我趴在偏房一處隱蔽的房頂上張望赤套。 院中可真熱鬧,春花似錦宣脉、人聲如沸唯沮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽币旧。三九已至,卻和暖如春巍虫,著一層夾襖步出監(jiān)牢的瞬間鳍刷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工瓦胎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柬祠。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓负芋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莽龟。 傳聞我的和親對象是個殘疾皇子蚜点,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容