1. 程式人生 > 實用技巧 >解決非同步程式設計的方法:promise與await

解決非同步程式設計的方法:promise與await

promise是什麼?

Promise,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。簡單來說,promise的作用就是將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。

promise的特點

① 物件的狀態不受外界影響:promise非同步操作有三種狀態:進行中,已成功,已失敗。只有非同步操作才能改變這個狀態。 ②一變則不變:promise狀態一旦改變,就不會再發生變化,promise物件改變的兩種可能,進行中—>已成功,進行中—>已失敗。

promise的基本用法

promise物件是一個建構函式,用來生成promise例項
例子:

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

  if (/* 非同步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

其中接受的引數是resolve和reject兩個函式
resolve的作用:將promise物件的狀態由進行中—>已完成。並將非同步操作的結果作為引數傳遞出去
rejected的作用:將promise物件的狀態由進行中—>已失敗,並將非同步失敗的原因作為引數傳遞出去。
注意:呼叫resolve或reject並不會終結promise的引數函式的執行
例子:

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

上面程式碼呼叫了resolve(1)以後,後面的console(2)還是會執行,並且會首先打印出來。這是因為立即resolved的promise是在本輪事件迴圈的末尾執行,總是晚於本輪迴圈的同步任務。

then的用法

promise例項生成後,用then方法分別指定resolved狀態和rejucted狀態的回撥函式。
例子:

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受兩個回撥函式作為引數,第一個回撥函式是當promise物件狀態是resolve(已完成)的時候呼叫,第二個回撥函式(可選)是當promise物件狀態是reject(已失敗)的時候呼叫。
如例子:

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

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

鏈式的then用法

then方法返回的是一個新的Promise例項(注意,不是原來那個Promise例項)。因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法
例子

getjsON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function funcA(comments) {
  console.log("resolved: ", comments);
}, function funcB(err){
  console.log("rejected: ", err);
});

上面程式碼中,第一個then方法指定的回撥函式,返回的是另一個Promise物件。這時,第二個then方法指定的回撥函式,就會等待這個新的Promise物件狀態發生變化。如果變為resolved,就呼叫funcA,如果狀態變為rejected,就呼叫funcB。

catch方法

promise物件中,如果非同步操作丟擲錯誤,狀態就會變為rejected,就會呼叫catch方法指定的回撥函式處理這個錯誤,另外,then方法指定的回撥函式,如果執行中丟擲錯誤也會被catch方法捕獲。
例子:

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

promise物件的錯誤具有“冒泡”性質,會一直向後傳,直到被捕獲,也就是說,會跳過中間的then函式
例子:

 getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 處理前面三個Promise產生的錯誤
});

finally方法

finally方法用於指定不管promise物件最後狀態如何,都會執行的操作。
例子

server.listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

例子是伺服器使用promise處理請求,然後使用finally()方法關掉伺服器。

promise.all()方法

promise.all方法用於將多個promise例項,包裝成一個新的promise例項。
比如:constp = Promise.all([p1, p2, p3]);
Promise.all方法,接受的是一個數組作為引數,其中的元素都是promise例項,如果不是,則會自動將引數轉變為promie例項。
p的狀態是有它的數組裡面的元素決定的,分兩種狀態(用上面舉例)
①只有p1 p2 p3的狀態都變成fulfilled(已完成)的狀態才會變成fulfilled(已完成),此時p1 p2 p3的返回值組成一個數組,傳遞給p的回撥函式。
②只有p1 p2 p3之中,有一個被rejucted(未完成),p的狀態就會變成rejected(未完成),此時第一個被reject的例項的返回值,會傳遞給p的回撥函式。
例子:

const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);

const p2 = new Promise((resolve, reject) => {
throw new Error('報錯了');
})
.then(result => result);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 報錯了enter code here

promise.race()方法

Promise.race方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項

const p = Promise.race([p1, p2, p3]);

如果p1 p2 p3不是promise例項,也會自動轉變成promise例項
與promise.all不同的是,上面程式碼中,只用p1、p2、p3之中有一個例項率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給p的回撥函式。

async函式是什麼

async的引入,使得非同步操作變得更加方便,那async函式是什麼,其實它是Generator函式的語法糖。使非同步函式、回撥函式在語法上看上去更像同步函式。Generator這裡就不介紹了。我們直接來學習async

async的基本用法

async返回值是一個promise物件,因此可以使用then方法添加回調函式,當函式執行的時候,一旦遇到await就會先返回,等到非同步操作完成,再接著執行函式體內後面的內容。
例子:

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

async函式的使用形式

//函式宣告
async function foo(){}
//函式表示式
const foo = async function(){};
//物件的方法
let obj = {async  foo(){}};
obj.foo().then(...)
//class的方法
class Storage{
consttuctor(){
    this.cachePromise=caches.open('avatars');
    }
    async getAvatar(name){
    const cache = await this.cachePromise;
    return cache.match('/avatars/${name}.jpg')};
    }
}

const storage =new Storage();
storage.getAvatar('jake').then(....);
}
}

const storage =new Storage();
storage.getAvatar('jake').then(...)

//箭頭函式
const foo =async()=>{};

async 函式內部return語句返回的值,會成為then方法呼叫函式的引數。
例子:

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

await 命令

正常情況下,await命令後面跟著的是一個promise物件,如果不是會自動轉化為promise物件
例子:

async function f(){
return await 123;
}
f().then(v =>console.log(v))
//123

當一個await語句後面的promise變為reject,那麼整個函式都會中斷執行。
例子:

async function f() {
  await Promise.reject('出錯了');
  await Promise.resolve('hello world'); // 不會執行
}

資源搜尋網站大全 https://www.renrenfan.com.cn

錯誤處理

如果await 後面的非同步操作有錯,那麼等同於async函式返回的promis物件被reject (上文講promise物件的時候有提到過,冒泡性質) 例子:

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出錯了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯了

使用try ....catch程式碼塊課防止出錯。
例子:

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出錯了');
    });
  } catch(e) {
  }
  return await('hello world');
}

也可以將多個await命令都放在try..catch結構中

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

注意點

①await命令只能用在async函式中,用在普通函式中會報錯。