1. 程式人生 > 其它 >Promise物件和async函式

Promise物件和async函式

非同步程式碼

現在常見的的非同步程式碼

  • 定時器
  • ajax 請求

注意事項

1.在 JavaScript 中,記住一件事兒:所有的非同步回撥函式執行一定在普通程式碼執行之後

2.如果想要獲取非同步程式碼的執行結果:通過回撥函式來接收

基於回撥函式的非同步流程控制

封裝一個原生 get 請求

function get(url, cd) {
 const xhr = new XMLHttpRequest();
 xhr.open("get", url);
 xhr.send();
 xhr.onload = function () {
  cd(this.response);
 };
}

原生 ajax 請求注意事項

事件最好放在傳送請求前 不然像捕獲傳送失敗這種函式會獲不到

const xhr = new XMLHttpRequest();
xhr.addEventListener("load", function () {
 resolve(this.response);
});
xhr.addEventListener("error", function (err) {
 reject(err);
});
xhr.open("get", url);
xhr.send();

非同步並行

  • 一起執行 不分先後順序
get("http://jsonplaceholder.typicode.com/posts", function (res) {
 console.log(1);
});
get("http://jsonplaceholder.typicode.com/comments", function (res) {
 console.log(2);
});
get("http://jsonplaceholder.typicode.com/users", function (res) {
 console.log(3);
});

非同步序列

  • 依次執行 上一層執行完畢在執行下面程式碼
get("http://jsonplaceholder.typicode.com/posts", function (res) {
 console.log(1);
 get("http://jsonplaceholder.typicode.com/comments", function (res) {
  console.log(2);
  get("http://jsonplaceholder.typicode.com/users", function (res) {
   console.log(3);
  });
 });
});

回撥地獄

非同步序列如果巢狀過深會造成經典的回撥地獄

這時需要用到 es6 新增的 Promise 物件來解決

axios 的序列

axios({
 method: "GET",
 url: "http://jsonplaceholder.typicode.com/posts",
})
 .then((res) => {
  console.log("2 posts 的響應結果");
  return axios({
   method: "GET",
   url: "http://jsonplaceholder.typicode.com/users",
  });
 })
 .then((res) => {
  console.log("3 users 的響應結果");
  return axios({
   method: "GET",
   url: "http://jsonplaceholder.typicode.com/comments",
  });
 })
 .then((res) => {
  console.log("4 comments 的響應結果");
 });

補充:setTimeout、setInterval 被遺忘的第三個引數

MDN介紹

定時器啟動時候,第三個以後的引數是作為第一個func()的引數傳進去。

var sum = function (x, y, z) {
 console.log(x + y + z); // 列印6
};
setTimeout(sum, 1000, 1, 2, 3);

Promise 物件

Promise 的含義

Promise 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函式和事件——更合理和更強大。它由社群最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise物件。

所謂Promise,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理。

Promise物件有以下兩個特點。

(1)物件的狀態不受外界影響。Promise物件代表一個非同步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise物件的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經發生了,你再對Promise物件添加回調函式,也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監聽,是得不到結果的。

注意,為了行文方便,本章後面的resolved統一隻指fulfilled狀態,不包含rejected狀態。

有了Promise物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函式。此外,Promise物件提供統一的介面,使得控制非同步操作更加容易。

Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。第三,當處於pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

如果某些事件不斷地反覆發生,一般來說,使用 Stream 模式(node.js)是比部署Promise更好的選擇。

基本用法

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

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

Promise建構函式接受一個函式作為引數,該函式的兩個引數分別是resolvereject。它們是兩個函式,由 JavaScript 引擎提供,不用自己部署。

resolve函式的作用是,將Promise物件的狀態從“未完成”變為“成功”(即從 pending 變為 resolved),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去;reject函式的作用是,將Promise物件的狀態從“未完成”變為“失敗”(即從 pending 變為 rejected),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。

Promise例項生成以後,可以用then方法分別指定resolved狀態和rejected狀態的回撥函式。

then方法可以接受兩個回撥函式作為引數。第一個回撥函式是Promise物件的狀態變為resolved時呼叫,第二個回撥函式是Promise物件的狀態變為rejected時呼叫。其中,第二個函式是可選的,不一定要提供。這兩個函式都接受Promise物件傳出的值作為引數。

下面是一個Promise物件的簡單例子。

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

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

上面程式碼中,timeout方法返回一個Promise例項,表示一段時間以後才會發生的結果。過了指定的時間(ms引數)以後,Promise例項的狀態變為resolved,就會觸發then方法繫結的回撥函式。

Promise-then 方法

  • then 方法執行完以後會返回一個新的 Promise 物件
  • 如果是普通資料,那麼它會把該資料包裝為那個返回的 Promise 的 resolve 結果
  • 如果你返回的資料就是一個 Promise 物件,那它就不做任何處理了

Promise.all

* Promise.all()方法用於將多個 Promise 例項,包裝成一個新的 Promise 例項。

* 所有引數的狀態都變成fulfilled,res的狀態才會變成fulfilled

Promise.race

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

* 引數之中有一個例項率先改變狀態,res的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給p的回撥函式

Promise.allSettled

* Promise.allSettled()方法接受一組 Promise 例項作為引數,包裝成一個新的 Promise 例項。

* 只有等到所有這些引數例項都返回結果,不管是fulfilled還是rejected,包裝例項才會結束。

Promise.any

​ * Promise.any()方法。該方法接受一組 Promise 例項作為引數,包裝成一個新的 Promise 例項返回。

​ * 只要引數例項有一個變成fulfilled狀態,包裝例項就會變成fulfilled狀態;

async 函式

Async 函式簡化了 Promise 的呼叫,本質還是 Promise,有點類似 promise 的 then 方法

任何函式都可以被標記為 async 函式

 // 具名函式:async function main() {
    // 匿名函式:async function () {}
    // 箭頭函式:async () => {}
    // 物件函式成員簡寫:

    // const user = {
    //   sayHello: async function () {}
    //   sayHello: async () => {}
注意物件裡函式的簡寫  async寫在方法名的前面 其他的放在函式前即可
 //   async sayHello () {}
    // }

async 函式-返回值

  • async 函式始終返回 Promise
  • async 函式的返回值
    • 如果是普通資料,則直接把它包裝到 promise 物件中,資料就是 resolve 的結果
    • 如果你返回的直接就是一個 promise 物件,則不作任何處理
    • 如果你什麼都沒有反回,則返回一個空的 promise 物件

await

await 代表等待的意思,就是等待後面的 promise 執行完返回後 在執行後續程式碼 ,相當於將後面的程式碼改成同步執行

async function ayrequest() {
 const res = await request("http://jsonplaceholder.typicode.com/users");
 console.log(1); //此程式碼會等待上面程式碼返回後再執行
 request("http://jsonplaceholder.typicode.com/users");
 console.log(2); // 此程式碼不會等待上面程式碼返回 , 直接執行
}

async 函式-異常處理

跟推薦使用 try catch 語法來獲取異常

async function ayrequest() {
 const res = await request("http://jsonplaceholder.typicode.com/users");
 return request("http://jsonplaceholder.typicode.com/posts");
 // 異常處理
 try {
  console.log(1);
  const res = await request("sdf://dsfsdfsdf.sdf.com/dsf");
  console.log(2);
  console.log(res);
 } catch (error) {
  console.log(3);
  console.log("傳送失敗", error);
 }
}

在 Vue 中使用 async 函式

async created () {
    axios({
      method: 'GET',
      url: 'http://jsonplaceholder.typicode.com/posts'
    }).then(res => {
      console.log(res)
    })
 }

利用Promise + aysnc 實現對原生ajax的封裝

這裡利用 module 的 頂端可以直接使用 await 的特性

<script type="module">
  function get(url) {
     // 返回一個封裝的 Promise
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.addEventListener("load", res => {
        let reseult = JSON.parse(res.target.response || "{}");
        // 成功resolve返回結果 狀態變為 fulfilled (已成功)
        resolve(reseult);
      });
      xhr.addEventListener("error", err => {
        // 失敗reject返回錯誤 狀態變為 rejected (已失敗)
        reject(err);
      });
      xhr.open("get", url);
      xhr.send();
    });
  }
   // 使用 await 等待 Promise的 返回值
   let res = await get("http://localhost:8848/getSysDB");
</script>

使用Promise的注意事項

將多個Promise包裝成一個Promise的方法 ,都是並行執行

  • Promise.all
  • Promise.race
  • Promise.allSettled
  • Promise.any

這幾個方法都用於將Promise 例項作為引數,包裝成一個新的 Promise 例項返回

這幾個方法在執行的 Promise 時候,都是並行執行

注意:如果有先執行完某個Promise 在執行某個 Promise 的時候不可以這幾種方法,需要改序列執行(一個執行完再執行另一個)