JS中的Promise與Async/Await

Promise 的含義

Promise 是異步編程的一種解決方案禽拔,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大刘离。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn)睹栖,統(tǒng)一了用法硫惕,原生提供了Promise對象。

從語法上說野来,Promise 是一個對象恼除,從它可以獲取異步操作的消息。

// 傳統(tǒng)寫法 回調(diào)地獄
step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // ...
      });
    });
  });
});

// Promise 的寫法
(new Promise(step1))
  .then(step2)
  .then(step3)
  .then(step4);

Promise 對象的狀態(tài)

Promise 對象通過自身的狀態(tài)曼氛,來控制異步操作豁辉。Promise 實(shí)例具有三種狀態(tài)。

  1. 異步操作未完成(pending)
  2. 異步操作成功(fulfilled)
  3. 異步操作失斠ɑ肌(rejected)

上面三種狀態(tài)里面徽级,fulfilled和rejected合在一起稱為resolved(已定型)。

這三種的狀態(tài)的變化途徑只有兩種构舟。

  1. 從“未完成”到“成功”
  2. 從“未完成”到“失敗”

一旦狀態(tài)發(fā)生變化灰追,就凝固了,不會再有新的狀態(tài)變化狗超。這也是 Promise 這個名字的由來弹澎,它的英語意思是“承諾”,一旦承諾成效努咐,就不得再改變了苦蒿。

創(chuàng)建一個Promise對象

Promise 構(gòu)造函數(shù)

var promise = new Promise(function (resolve, reject) {
  // ... some code

  if (/* 異步操作成功 */){
    resolve(value);
  } else { /* 異步操作失敗 */
    reject(new Error());
  }
});

該函數(shù)的兩個參數(shù)分別是resolve和reject。它們是兩個函數(shù)渗稍,由 JavaScript 引擎提供佩迟,不用自己部署。

Promise.resolve 方法

將一個普通對象生成一個Promise對象

Promise.resolve()等價(jià)于下面的寫法竿屹。

Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))

Promise.reject 方法

生成一個新的 Promise 實(shí)例报强,該實(shí)例的狀態(tài)為rejected。

const p = Promise.reject('出錯了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯了'))

p.then(null, function (s) {
  console.log(s)
});
// 出錯了

Promise 狀態(tài)的處理方法

Promise.prototype.then 方法

Promise 實(shí)例具有then方法拱燃,它的作用是為 Promise 實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)秉溉。

then方法的第一個參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)(該參數(shù)可以省略)。一旦狀態(tài)改變召嘶,就調(diào)用相應(yīng)的回調(diào)函數(shù)父晶。

下面是一個Promise對象的簡單例子。Promise 新建后就會立即執(zhí)行弄跌。

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('done')
    }, ms);
  });
}

timeout(100).then(value => {
  console.log(value); // done
});

then方法返回的是一個新的Promise實(shí)例(注意甲喝,不是原來那個Promise實(shí)例)。因此可以采用鏈?zhǔn)綄懛踔唬磘hen方法后面再調(diào)用另一個then方法埠胖。

采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)淳玩。這時(shí)押袍,前一個回調(diào)函數(shù),有可能返回的還是一個Promise對象(即有異步操作)凯肋,這時(shí)后一個回調(diào)函數(shù)谊惭,就會等待該P(yáng)romise對象的狀態(tài)發(fā)生變化,才會被調(diào)用侮东。

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);

Promise 對象的報(bào)錯具有傳遞性圈盔,會一直向后傳遞,直到被捕獲為止悄雅。如果不設(shè)置回調(diào)函數(shù)驱敲,Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部宽闲。比如下面的例子:

p1
  .then(step1)
  .then(step2)
  .then(step3)
  .then(
    console.log,
    console.error
  );

如果step1的狀態(tài)變?yōu)閞ejected众眨,那么step2和step3都不會執(zhí)行了(因?yàn)樗鼈兪莚esolved的回調(diào)函數(shù))。Promise 開始尋找容诬,接下來第一個為rejected的回調(diào)函數(shù)

Promise.prototype.catch 方法

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名娩梨,用于指定發(fā)生錯誤時(shí)的回調(diào)函數(shù)。

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同于
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

上面代碼中览徒,如果p的狀態(tài)變?yōu)閞esolved狈定,則會調(diào)用then()方法指定的回調(diào)函數(shù);如果異步操作拋出錯誤习蓬,狀態(tài)就會變?yōu)閞ejected纽什,就會調(diào)用catch()方法指定的回調(diào)函數(shù),處理這個錯誤躲叼。另外芦缰,p.then()方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯誤枫慷,也會被catch()方法捕獲让蕾。

一般來說包斑,不要在then()方法里面定義 Reject 狀態(tài)的回調(diào)函數(shù)(即then的第二個參數(shù)),總是使用catch方法涕俗。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

上面代碼中,第二種寫法要好于第一種寫法神帅,理由是第二種寫法可以捕獲前面then方法執(zhí)行中的錯誤再姑,也更接近同步的寫法(try/catch)。因此找御,建議總是使用catch()方法元镀,而不使用then()方法的第二個參數(shù)。

Promise.prototype.finally 方法

finally()方法用于指定不管 Promise 對象最后狀態(tài)如何霎桅,都會執(zhí)行的操作栖疑。該方法是 ES2018 引入標(biāo)準(zhǔn)的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

上面代碼中滔驶,不管promise最后的狀態(tài)遇革,在執(zhí)行完then或catch指定的回調(diào)函數(shù)以后,都會執(zhí)行finally方法指定的回調(diào)函數(shù)揭糕。

Nodejs中的Promise

nodejs8以上已經(jīng)原生支持es6語法書寫代碼了萝快,雖然 Promise 已經(jīng)普及,但是 Node.js 里仍然有大量的依賴回調(diào)的異步函數(shù)著角,

所以 Node8 就提供了 util.promisify() 這個方法揪漩,方便我們快捷的把原來的異步回調(diào)方法改成返回 Promise 實(shí)例的方法

// 異步回調(diào)形式
fs.readFile('./test.js',function(err, data){
    console.log(data)
})

// promisify 形式
const readFileAsync = Promise.promisify(fs.readFile)

readFileAsync('./test.js').then(function(data){
    console.log(data)
}).catch(err){
    console.log(err)
}

Async/Await

ES2017(ES8) 標(biāo)準(zhǔn)引入了 async 函數(shù),使得異步操作變得更加方便吏口。async/await使得異步代碼看起來像同步代碼奄容,這正是它的魔力所在。

async/await是基于Promise實(shí)現(xiàn)的产徊,它不能用于普通的回調(diào)函數(shù)昂勒。

基本用法

async

async函數(shù)返回一個 Promise 對象。

async函數(shù)內(nèi)部return語句返回的值舟铜,會成為then方法回調(diào)函數(shù)的參數(shù)叁怪。

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

async函數(shù)內(nèi)部拋出錯誤,會導(dǎo)致返回的 Promise 對象變?yōu)閞eject狀態(tài)深滚。拋出的錯誤對象會被catch方法回調(diào)函數(shù)接收到奕谭。

async function f() {
  throw new Error('出錯了');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出錯了

await

await命令后面是一個 Promise 對象,返回該對象的結(jié)果痴荐。如果不是 Promise 對象血柳,就直接返回對應(yīng)的值。

await只能用在async方法內(nèi)部生兆。

async function f() {
  let result = await Promise.resolve('hello world');
  console.log(result) // hello world
}

Promise與async/await

promise改造為async/await的寫法:

// promise
const stat = util.promisify(fs.stat);
stat('.')
 .then((stats) => {
  // Do something with `stats`
 })
 .catch((error) => {
  // Handle the error.
 });

// async/await
const stat = util.promisify(fs.stat);
async function readStats(dir) {
 try {
  let stats = await stat(dir);
  // Do something with `stats`
 } catch (err) { // Handle the error.
  console.log(err);
 }
}
readStats('.');
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末难捌,一起剝皮案震驚了整個濱河市膝宁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌根吁,老刑警劉巖员淫,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異击敌,居然都是意外死亡介返,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門沃斤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來圣蝎,“玉大人,你說我怎么就攤上這事衡瓶∨枪” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵哮针,是天一觀的道長关面。 經(jīng)常有香客問我,道長十厢,這世上最難降的妖魔是什么缭裆? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮寿烟,結(jié)果婚禮上澈驼,老公的妹妹穿的比我還像新娘。我一直安慰自己筛武,他們只是感情好缝其,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著徘六,像睡著了一般内边。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上待锈,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天漠其,我揣著相機(jī)與錄音,去河邊找鬼竿音。 笑死和屎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的春瞬。 我是一名探鬼主播柴信,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宽气!你這毒婦竟也來了随常?” 一聲冷哼從身側(cè)響起潜沦,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绪氛,沒想到半個月后唆鸡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枣察,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年争占,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片询件。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖唆樊,靈堂內(nèi)的尸體忽然破棺而出宛琅,到底是詐尸還是另有隱情,我是刑警寧澤逗旁,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布嘿辟,位于F島的核電站,受9級特大地震影響片效,放射性物質(zhì)發(fā)生泄漏红伦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一淀衣、第九天 我趴在偏房一處隱蔽的房頂上張望昙读。 院中可真熱鬧,春花似錦膨桥、人聲如沸蛮浑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沮稚。三九已至,卻和暖如春册舞,著一層夾襖步出監(jiān)牢的瞬間蕴掏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工调鲸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盛杰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓藐石,卻偏偏與公主長得像饶唤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贯钩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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