JS中的Promise與Async/Await

Promise 的含義

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

從語(yǔ)法上說(shuō),Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息。

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

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

Promise 對(duì)象的狀態(tài)

Promise 對(duì)象通過(guò)自身的狀態(tài),來(lái)控制異步操作。Promise 實(shí)例具有三種狀態(tài)。

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

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

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

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

一旦狀態(tài)發(fā)生變化,就凝固了,不會(huì)再有新的狀態(tài)變化。這也是 Promise 這個(gè)名字的由來(lái),它的英語(yǔ)意思是“承諾”,一旦承諾成效,就不得再改變了。

創(chuàng)建一個(gè)Promise對(duì)象

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

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

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

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

Promise.resolve 方法

將一個(gè)普通對(duì)象生成一個(gè)Promise對(duì)象

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

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

Promise.reject 方法

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

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

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

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

Promise.prototype.then 方法

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

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

下面是一個(gè)Promise對(duì)象的簡(jiǎn)單例子。Promise 新建后就會(huì)立即執(zhí)行。

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

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

then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄?xiě)法,即then方法后面再調(diào)用另一個(gè)then方法。

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

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

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

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

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

Promise.prototype.catch 方法

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(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,則會(huì)調(diào)用then()方法指定的回調(diào)函數(shù);如果異步操作拋出錯(cuò)誤,狀態(tài)就會(huì)變?yōu)閞ejected,就會(huì)調(diào)用catch()方法指定的回調(diào)函數(shù),處理這個(gè)錯(cuò)誤。另外,p.then()方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤,也會(huì)被catch()方法捕獲。

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

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

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

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

Promise.prototype.finally 方法

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

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

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

Nodejs中的Promise

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

所以 Node8 就提供了 util.promisify() 這個(gè)方法,方便我們快捷的把原來(lái)的異步回調(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使得異步代碼看起來(lái)像同步代碼,這正是它的魔力所在。

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

基本用法

async

async函數(shù)返回一個(gè) Promise 對(duì)象。

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

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

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

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

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

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

await

await命令后面是一個(gè) Promise 對(duì)象,返回該對(duì)象的結(jié)果。如果不是 Promise 對(duì)象,就直接返回對(duì)應(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的寫(xiě)法:

// 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)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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