ES系列之Promise async 和 await

概述

promise是異步編程的一種解決方案,比傳統的解決方案—回調函數和事件—更合理更強大。編程

所謂的promise就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做的結果)。json

Promise是一個對象,從它這裏能夠獲取異步操做的信息, Promise提供統一的API,各類異步操做均可以用一樣的方法處理。數組

promise的特色:promise

  1. 對象的狀態不受外界影響,promise對象表明一個異步操做,有三種狀態:pending(進行中)Fulfilled(已成功)Rejected(已失敗)。只有異步操做的結果才能夠決定當前是哪種操做狀態,任何其餘操做都沒法改變這種狀態。併發

  2. 一旦狀態改變就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變只有兩種可能:從pending(進行中)變爲Fulfilled(已成功)、或者從pending(進行中)變成Rejected(已失敗)。狀態發生改變就凝固了,不會再變,而是一直保持這個結果,這是就稱爲Resolved(已定型)。就算改變已經發生,再對Promise對象添加回調函數,也會當即獲得這個結果,這個與event徹底不一樣,事件的特色是,若是錯過了它,再去監聽是得不到結果的。app

    有了promise對象,就能夠將異步操做以同步操做表示出來,避免了層層嵌套的回調函數異步

    Promise的缺點:async

    1. 沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。異步編程

    2. 若是不設置回調函數,Promise內部拋出錯誤不會反應到外部。函數

    3. 當處於Pending狀態時,沒法得知目前進展到哪個階段了。

基本用法

   let promise = new Promise(function(res, rej) {
       // your code...
   })
   
   promise.then(function(value) {
       // res
   }, function(error) {
       // rej
   })

執行順序

let promise = new Promise(function(resolve, reject) {
  // resolve, reject 纔是真正的異步
  console.log('1')
  resolve()
})

promise.then(() => {
  console.log('2')
})

console.log('3')

// 1 3 2

異步加載圖片

// 異步加載圖片
function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    let image = new Image()

    iamge.onload = function () {
      resolve(image)
    }

    image.onerror = function() {
      reject(new Error('不能加載' + url))
    }

    iamge.src = url
  })
}

模擬AJAX

// promise對象實現AJAX
// getJSON 是對XMLHttpReqest對象的封裝,用於發出一個JSON數據的HTTP請求
// 並返回一個Promise對象。須要注意的是,在getJSON內部,resolve函數和reject函數
// 都帶有參數

let getJSON = function(url) {
  let promise = new Promise(function(resolve, reject) {
    let client = new XMLHttpRequest()
    client.open('GET', url)
    client.onreadystatechange = handler
    client.responseType = "json"
    client,setRequestHeader('Accept','application/json')
    client.send()

    function handler() {
      if(this.readyState !== 4) {
        return
      }
      if(this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
  })
  return promise
}

getJSON('/posts.json').then(function(json) {
  console.log('Contents:' + json)
},function(error) {
  console.log('error',error)
})

promise中的promise

/**
 * 若是resolve 和 reject 函數都帶有參數,那麼這些參數會被傳遞到回調函數。
 * reject函數接收Error對象的實例,表示拋出的錯誤
 * reslove 函數參數除了正確的值,還有多是一個Promise對象
 */


let p1 = new Promise(function(resolve, reject) {
  // ...
})

let p2 = new Promise(function(resolve, reject) {
  // ...
  resolve(p1)
})
/**p1 和 p2 都是Promise的實例,可是p2的resolve方法將p1做爲參數
 * 即一個異步操做的結果是返回另外一個異步操做。
 * p1的狀態傳遞給p2.p1的狀態決定了p2的狀態
 */

 

let p1 = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

let p2 = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2.then(result => console.log(result)).catch(error => console.log(error))

/**
 * p1 是 一個Promise,3s以後變成rejected。
 * p2 的狀態在1s後改變,resolve方法返回的是p1
 */

Promise.prototype.then()

做用:爲了Promise實例添加狀態改變時的回調。

promise.then(function(value) {
    // res
}, function(error) {
    // rej
})

// then方法有兩個參數,第一個參數是Resolved狀態的回調函數,第二個參數(可選)是Rejected狀態的回調函數。
// then返回的是一個新的Promise實例

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的別名,用於指定發生錯誤的回調函數。

另外then方法指定的回調函數若是在運行中拋出錯誤,也會被catch捕獲。

通常來講,不要在then方法中定義Rejected狀態的回調(即then的第二個參數),而應該老是使用catch方法。

p2.then(result => console.log(result))
.catch(error => console.log(error))
// .catch 處理 p2 和前一個回調函數運行時發生的錯誤

// ====> 等同於
p2.then(result => console.log(result))
.then(null, err => console.log(err))

Promise.all()

Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3]); 
// Promise.all方法接受一個數組做爲參數,p一、p二、p3都是 Promise 實例,若是不是,就會先調用下面講到的Promise.resolve方法, 將參數轉爲 Promise 實例,再進一步處理。

p的狀態由p一、p二、p3決定,分紅兩種狀況。

1)只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。

2)只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

const databasePromise = connectDatabase();

const booksPromise = databasePromise.then(findAllBooks);

const userPromise = databasePromise.then(getCurrentUser);

// booksPromise和userPromise是兩個異步操做,只有等到它們的結果都返回了,纔會觸發pickTopRecommentations這個回調函數。
// 若是做爲參數的 Promise 實例,本身定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()的catch方法
Promise.all([booksPromise, userPromise]).then(([books, user]) => pickTopRecommentations(books, user));

async函數

ES7標準引入了async函數,使得異步操做變得簡單,——async就是Generator的語法糖。

用法

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關鍵字代表該函數內部有異步操做。
// 二、調用該函數會當即返回一個Promise對象

指定多少毫秒輸出一個值

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

async function asyncPrint(value, ms) {
  // 一旦趕上await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 3000); // 3s以後返回 'hello world'

/** ------------- 改寫 ------------------ */
async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 3000);

語法

async函數返回一個promise對象,async函數內部return語句返回的值,會成爲then方法回調函數的參數。

Promise 對象的狀態變化

async函數返回的 Promise對象,必須等到內部全部await命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到return語句或者拋出錯誤。

也就是說,只有async函數內部的全部異步操做執行完,纔會執行then方法指定的回調函數。

正常狀況下,await命令後面是一個 Promise 對象。若是不是,會被轉成一個當即resolve的 Promise 對象。

async function f() {
  return await 123;
}

// await命令的參數是數值123,它被轉成 Promise 對象,並當即resolve。

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

await命令後面的 Promise 對象若是變爲reject狀態,則reject的參數會被catch方法的回調函數接收到。

async function f() {
  await Promise.reject('出錯了');
}

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

只要一個await語句後面的 Promise 變爲reject,那麼整個async函數都會中斷執行。

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

 

如今,須要在第一個異步操做失敗,也不要中斷後面的異步操做。

這是能夠將第一個await放在try...catch...結構裏面,這樣無論這個異步操做返回的結果是resolve仍是reject都會執行以後的await操做。

async function f() {
  try {
    await Promise.reject('出錯了');
  } catch (e) {
    console.log(e);
  }
  return await Promise.resolve('hello world');
}

f().then(v => console.log(v)) // hello world

還有一種方法,就是在第一個await以後使用catch接收失敗的回調,處理前面出現的錯誤。

async function f() {
  await Promise.reject('出錯了').catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f().then(v => console.log(v)) // 出錯了 // hello world

錯誤處理

若是await後面的異步操做出錯了,那麼等同於async函數返回的Promise對象被reject了。

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

f().then(v => console.log(v)).catch(e => console.log(e)) // Error:出錯了
// async函數f執行後,await後面的 Promise 對象會拋出一個錯誤對象,
// 致使catch方法的回調函數被調用,它的參數就是拋出的錯誤對象。

 

// 有多個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使用注意點

第一點,前面已經說過,await命令後面的Promise對象,運行結果多是rejected,因此好把await命令放在try...catch代碼塊中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另外一種寫法 

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err) {
    console.log(err);
  });
}

第二點,多個await命令後面的異步操做,若是不存在繼發關係,好讓它們同時觸發

// 寫法一 
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 寫法二 
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

第三點,await命令只能用在async函數之中,若是用在普通函數,就會報錯。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  // 報錯
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

// 若是將forEach方法的參數改爲async函數,也有問題。
function dbFuc(db) { //這裏不須要 async
  let docs = [{}, {}, {}];
  // 可能獲得錯誤結果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
// 緣由是這時三個db.post操做將是併發執行,也就是同時執行,而不是繼發執行。正確的寫法是採用for循環。

 

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  for (let doc of docs) {
    await db.post(doc);
  }
}

 

// 若是確實但願多個請求併發執行,可使用Promise.all方法。當三個請求都會resolved時,下面兩種寫法效果相同。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的寫法 

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}

實例

 var pro = new Promise(function(){
      var a = 1;
})
console.log(pro); 

讀文件的方法

const fs = require('fs')

// 總結:只要 new 了一個具體的異步操做,這個異步操做被建立的一瞬間,就會當即執行;
function readFileByPath(fpath) {
  const p = new Promise(function() {
    fs.readFile(fpath, 'utf-8', (err, result) => {
      if (err) return console.log('讀文件失敗:' + err.message)
      console.log(result)
    })
  })
}

readFileByPath('./files/3.txt') // 文件必須存在

使用.then()進一步封裝讀文件的操做

封裝原則:不要在方法內部顯示結果,要把結果返回給調用者,不要提調用者作決定!!

 使用.catch()進一步封裝讀文件的操做

const fs = require('fs')

function readFileByPath(fPath) {
  return new Promise(function(resolve,reject){
    fs.readFile(fPath, 'utf-8', (err, result) => {
      if (err) return reject(arr)
      resolve(result)
    })
  })
}

// 通常.then()方法中,失敗的回調能夠省略,可是省略之後讀取文件失敗時,沒法接收結果
// 這是,咱們可使用.catch() 來指定失敗的回調
/* --------------讀一個-------------------- */
readFileByPath('./files/1.txt')
  .then(function(result) {
    console.log(result)
  })
  .catch(err => console.log(err.message))

/* ---------------讀多個----------------------- */
readFileByPath('./files/1.txt')
  .then(function(result){
    console.log(result)
    return readFileByPath('./files/2.txt')
  })
  .then(function(result){
    console.log(result)
    return readFileByPath('./files/3.txt')
  })
  .catch(err => console.log(err.message))

 async和await

const fs = require('fs')

function readFileByPath(fPath) {
  return new Promise(function(resolve,reject){
    fs.readFile(fPath, 'utf-8', (err, result) => {
      if (err) return reject(arr)
      resolve(result)
    })
  })
}

// async 用來修飾異步方法
// await 只能用在被 async 修飾的方法中
// 同時,await 是用來修飾 Promise 實例對象的;簡化promise對象

console.log("開始");
async function readAll () {
  console.log("方法頭部");
  const result1 = await readFileByPath('./files/1.txt')
  console.log(result1)
  const result2 = await readFileByPath('./files/2.txt')
  console.log(result2)
  const result3 = await readFileByPath('./files/3.txt')
  console.log(result3)
  console.log("方法尾部");
}
console.log("結束");

readAll()

運行順序:

緣由:

/* async修飾的 readAll()方法異步方法,在調用這個readAll()方法的時候,js主線程進入這個方法
這時,仍是主線程在執行,輸出「方法頭部」,而後在 await是異步操做,這是主線程就退出這個方法,
執行「結束」,而後就是異步的方法順序執行 */
相關文章
相關標籤/搜索