異步編程解決方案——Promise對象(ES6語法)

JavaScript異步編程的六種方案

  • 回調函數
  • 事件監聽
  • 事件發佈/訂閱模式
  • Promise
  • 生成器Generators/ yield
  • async/await

JS 異步編程進化史:callback -> promise -> generator -> async/await編程

Promise對象

Promise是什麼

promise即承諾,異步編程的一種解決方式,內部保存異步操做,經過它能夠得到異步操做的消息json

特色數組

  • 對象的狀態不受外部影響。總共三種狀態,進行中pending、已成功fulfilled、已失敗rejected。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。
  • 一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。狀態改變只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。狀態凝固後,被稱爲resolved已定型。

優勢:接口統一,使得控制異步操做方便;解決回調地獄,將異步操做以同步操做的流程表現出來promise

缺點:中途沒法取消,一旦新建就當即執行;內部報錯沒法反應到外部(無回調函數的話);pending狀態是沒法知道具體執行到哪一步bash

new Promise(請求1)
    .then(請求2(請求結果1))
    .then(請求3(請求結果2))
    .then(請求4(請求結果3))
    .then(請求5(請求結果4))
    .catch(處理異常(異常信息))
複製代碼

基本用法

第一步,建立Promise實例app

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

  if (/* 異步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

resolve()將pending狀態變爲resolved狀態,同時傳出參數(通常是異步操做返回的結果)
reject()將pending狀態變爲rejected狀態,同時傳出參數(通常是錯誤信息)
複製代碼

第二步,指定回調函數異步

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

then方法能夠接受兩個回調函數做爲參數。
第一個回調函數是Promise對象的狀態變爲resolved時調用,
第二個回調函數是Promise對象的狀態變爲rejected時調用。
複製代碼

Promise 新建後當即執行async

Promise內部建立的代碼當即執行,回調函數放入任務隊列中,調用棧中的任務(即同步任務)都執行完才執行任務隊列中的任務。異步編程

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

輸出以下:
// Promise
// Hi!
// resolved
複製代碼

通常Promise都以函數返回的形式來建立,好處是封裝後在不一樣場合下能夠屢次使用函數

用Promise對象實現的 Ajax 操做

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出錯了', error);
});
複製代碼

resolve()reject()參數是另外一個Promise實例時,原Promise狀態失效,由新Promise的狀態決定原Promise的回調函數執行

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

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

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

分析以下:
p1三秒後進入rejected狀態,p2一秒後進入resolved狀態
因爲p2的參數爲p1,則一秒後,p2狀態失效,由p1決定p2的狀態
此時p1仍處於pending狀態
兩秒後,p1狀態變爲rejected,則p2狀態也變爲rejected
觸發回調函數.catch(),打印出p1的reject()中的參數
複製代碼

調用resolve或reject並不會終結 Promise 的參數函數的執行

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

在前面加個return就會終結了

new Promise((resolve, reject) => {
  return resolve(1);
  // 後面的語句不會執行
  console.log(2);
})
複製代碼

Promise.prototype

Promise.prototype.then()

參數

第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。

返回值

then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。

所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。

鏈式操做

第一個回調函數完成之後,會將返回結果做爲參數,傳入第二個回調函數。

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});
複製代碼

前一個回調函數,返回的是一個Promise對象(即有異步操做),後一個回調函數,就會等待該Promise對象的狀態發生變化,纔會被調用。

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

Promise.prototype.catch()

Promise.prototype.catch等價於.then(null, rejection).then(undefined, rejection)

建議用.catch()而不用.then()的第二個參數來捕獲錯誤

由於.catch()不只能夠處理Promise對象狀態變成rejected,還能捕獲.then()方法中的錯誤

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 getJSON 和 前一個回調函數運行時發生的錯誤
  console.log('發生錯誤!', error);
});
複製代碼

若是 Promise 狀態已經變成resolved,再拋出錯誤是無效的

Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止

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

Promise 會吃掉錯誤

Promise的定義若是有語法錯誤,控制檯會顯示,可是代碼不會中止運行,會繼續執行,即Promise 內部的錯誤不會影響到 Promise 外部的代碼

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報錯,由於x沒有聲明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
複製代碼

返回值也是一個Promise對象,能夠鏈式操做

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報錯,由於x沒有聲明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on

若是沒有報錯,則會跳過catch方法
複製代碼

Promise.prototype.finally()

finally方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做。

在執行完then或catch指定的回調函數之後,也還會執行finally方法指定的回調函數。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
複製代碼

回調函數不接受任何參數

沒法判斷狀態,就是爲了少寫相同的代碼(then和catch中都要寫的)

實現

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
複製代碼

返回值老是原來的值

// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
複製代碼

Promise方法

Promise.all()

做用:將多個 Promise 實例,包裝成一個新的 Promise 實例

語法const p = Promise.all([p1, p2, p3]);

參數:輸入一個數組,數組元素若不是Promise 實例,則會被Promise.resolve轉成實例

返回值:返回一個新的Promise 實例,p的狀態由p一、p二、p3決定

  • 只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
  • 只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
  • 若是做爲參數的 Promise 實例,本身定義了catch方法,那麼它一旦被rejected,並不會觸發Promise.all()的catch方法。

總結:邏輯與,全fulfilledfulfilled,一rejectedrejected

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

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

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 報錯了]
複製代碼

Promise.race()

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

上面代碼中,只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。

那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

總結:競賽,誰先返回就返回該值

// 若是指定時間內沒有得到結果,就將 Promise 的狀態變爲reject,不然變爲resolve。
const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);
複製代碼

Promise.allSettled()

只有等到全部這些參數實例都返回結果,不論是fulfilled仍是rejected,包裝實例纔會結束。

返回的新的 Promise 實例,一旦結束,狀態老是fulfilled

總結:都被設置,能解決Promise.all()的沒法知道全部操做都結束

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
複製代碼

監聽函數接收到的參數是數組results。

該數組的每一個成員都是一個對象,對應傳入Promise.allSettled()的兩個 Promise 實例。

每一個對象都有status屬性,該屬性的值只多是字符串fulfilled或字符串rejected。

fulfilled時,對象有value屬性,rejected時有reason屬性,對應兩種狀態的返回值。

Promise.any()

Promise.any()方法接受一組 Promise 實例做爲參數,包裝成一個新的 Promise 實例。

只要參數實例有一個變成fulfilled狀態,包裝實例就會變成fulfilled狀態;若是全部參數實例都變成rejected狀態,包裝實例就會變成rejected狀態。

總結:邏輯或,一fulfilledfulfilled,全rejectedrejected

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results); // [-1, Infinity]
});
複製代碼

Promise.resolve()

做用:將現有對象轉爲 Promise 對象

語法

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
複製代碼

參數

(1)參數是一個 Promise 實例

Promise.resolve將不作任何修改、原封不動地返回這個實例。

(2)參數是一個thenable對象

Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});
複製代碼

(3)參數不是具備then方法的對象,或根本就不是對象

Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved,回調函數的參數就是該參數。

const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello
複製代碼

(4)不帶有任何參數

直接返回一個resolved狀態的 Promise 對象。就是要注意回調執行的順序。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three
複製代碼

Promise.reject()

返回一個新的 Promise 實例,該實例的狀態爲rejected。

Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。

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

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

Promise.try()

讓同步函數同步執行,異步函數異步執行,而且讓它們具備統一的 API

如下的函數f()表示的是不肯定是同步或者異步的操做

// 方法一:
const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next
複製代碼
// 方法二:
(async () => f())()
.then(...)
.catch(...)
複製代碼
// 方法三:
Promise.try(() => f())
  .then(...)
  .catch(...)
複製代碼
相關文章
相關標籤/搜索