深入解析ES6中的promise

  • 2019 年 11 月 29 日
  • 筆記

file

作者 | Jeskson 來源 | 達達前端小酒館

什麼是Promise

Promise對象是用於表示一個異步操作的最終狀態(完成或失敗)以及其返回的值。

什麼是同步,異步

同步任務會阻塞程序的執行,如alert,for

異步任務不會阻塞程序的執行,如setTimeou

使用Promise,then,catch,finally

Promise.all 和 Promise.race

Promise.resolve 和 Promise.reject

回調與Promise

回調函數,用於請求數據

function backFunction(fn) {   setTimeout(function() {    fn && fn();   }, 1000);  }    // 調用  backFunction(function() {   console.log(1); // 1   backFunction(function() {    console.log(2); // 2     backFunction(function() {      console.log(3); // 3     });   });  });

Promise

function d() {   return new Promise(resolve = {    setTimeout(function() {     resolve(); // resolve成功的時候要做的事情    },1000);    // 1秒後調用resolve(),它是一個函數   })  }    d()   .then(function() {    console.log(1);    return d(); // Promise實例   })   .then(function() {    console.log(2);    return d(); // Promise實例   }).then(function() {    console.log(3);   });

對比回調

// 動畫  function moveTo(el, x, y, fn) {   el.style.transform = `translate(${x}px, ${y}px)`;   setTimeout(function() {    fn && fn();   },1000);  }    let el = document.querySelector('div');    document.querySelector('button').addeventListener('click', e   moveTo(el, 100, 100, function() {    console.log(1);    moveTo(el, 200, 200, function() {     console.log(2);    });   })  });
// promise  function moveTo(el,x,y) {   return new Promise(resolve => {    el.style.transform = `translate(${x}px, ${y}px)`;    setTimeout(function() {     resolve();    },1000);   });  }    document.querySelector('button').addEventListener('click', e=>{   moveTo(el,100,100)    .then(function() {     console.log('da');     return moveTo(el, 200, 200);    })    .then(function() {      console.log('da2');    }).then(function() {      console.log('da2');    });  });

信任問題

// 使用第三方庫 回調  function method(fn) {   // 回調   setTimeout(function() {    // 回調    fn && fn();    // 有可以有bug,被多調用一次    fn && fn();   },1000);  }    // promise一旦被調用,成功或者是失敗後,就不能再被修改    function method() {   return new Promise(resolve => {    setTimeout(function() {     //成功     resolve();     // 再調用就不會執行     resolve();    },1000);   });  }
// 控制反轉    function method(fn) {   setTimeout(function() {    // 執行回調    fn && fn.call({a:1, b:2)};    // 改變指向   },1000);  }    function method(fn) {   return new Promise(resolve => {    setTimeout(() => {     resolve();    },1000);   });  }

錯誤處理

then(resolve, reject) then方法中的第二個回調,是失敗的時候要做的事情

catch 使用實例的then方法,可以捕獲錯誤

finally 不論成功與否,finally中的內容一定會執行

function fn(val) {   return new Promise((resolve, reject) => {    if(val) {     resolve(); // 成功的時候    } else {     reject(); // 失敗的時候    }   });  }
fn(false)   .then( () => {    console.log("成功");   }, () => {    console.log("失敗");   })    function fn(val) {   return new Promise((resolve, reject) => {    if(val) {     resolve(); // 成功的時候    } else {     reject('404'); // 失敗的時候    }   });  }    fn(false)   .then( () => {    console.log("成功");   }, e => {    console.log(e);   })     promise 中resolve只能傳遞一個參數,如下:     function fn(val) {   return new Promise((resolve, reject) => {    if(val) {     resolve({name: 'da'}); // 成功的時候    } else {     reject('404'); // 失敗的時候    }   });  }    fn(true)   .then( dada => {    console.log(data);   }, e => {    console.log(e);   })

catch會捕獲錯誤,如果在回調中沒有對錯誤進行處理

fn(true)   .then(data => {    console.log(data);    return fn(false);   })   .then( () => {    console.log('da'); // 不會執行,沒處理錯誤   })   .then( () => {     })   .catch(e => {    console.log(e);    return fn(false);   }); // 直接輸出到這     不能保證catch被執行

如果沒有對失敗做出處理,會報錯

fn(true)   .then(data => {    console.log(data);    return fn(false);   })   .catch(e=> {    // 捕獲錯誤    console.log(e);    return fn(false);   })   .finally( () => {    console.log(100);   });

Promise的三種狀態

pending為進行中的狀態,fulfilled為成功的狀態,rejected為失敗的狀態。狀態的改變時不可返的,一旦決議就不能修改(決議,狀態的改變為決議),狀態只能從pending到fulfilled,或者,從pending到rejected。

Promise.all方法可以把多個promise的實例包裝成一個新的promise實例

Promise.all( [promise1, promise2] ) : Promise    數組中,如果promise都為true,則返回為true,決議為成功  如果數組中有一個為promise,那麼返回的是false,決議為失敗    如果是一個空數組,那麼返回為true,決議為成功

模式多個請求的數據

function getData1() {   return new Promise((resolve, reject) => {    setTimeout( () => {     console.log('第一條數據加載成功');     resolve('data1');    },1000);   });  }    function getData2() {   return new Promise((resolve, reject) => {    setTimeout( () => {     console.log('第二條數據加載成功');     resolve('data2');    },1000);   });  }    function getData3() {   return new Promise((resolve, reject) => {    setTimeout( () => {     console.log('第三條數據加載成功');     resolve('data3'); // 改為 reject('err')    },1000);   });  }
let p = Promise.all( [getData1(), getData2(), getData3()] );    p.then(arr => {   console.log(arr);  });    // 失敗    p.then(arr => {   console.log(arr);  }, e => {   console.log(e);  });    let p = Promise.all([]); // 決議為成功    p.then( () => {   console.log(`da`);  }, e => {   console.log(e);  });
第一條數據加載成功  第二條數據加載成功  第三條數據加載成功

不用Promise.all

let count = 0;  function getData1() {   setTimeout( () => {    console.log('第一條數據加載成功');    count ++;    func();    },1000);  }    function getData2() {   setTimeout( () => {    console.log('第二條數據加載成功');    count ++;    func();    },1000);  }    function getData3() {   setTimeout( () => {    console.log('第三條數據加載成功');    count ++;    func();    },1000);  }    function getData4() {   setTimeout( () => {    console.log('第四條數據加載成功');    count ++;    func();    },1000);  }    // 寫一個方法:  function func() {   if(count < 4)return;   console.log('全部拿到了');  }

調用

getData2();  getData3();  getData4();

file

let err = false;  function getData1() {   setTimeout( () => {    console.log('第一條數據加載成功');    if(status) err = true;    count ++;    func();    },1000);  }    function func() {   if(count < 4)return;   console.log('全部拿到了');     if(err) {    // ...   }  }

Promise.race()

Promise.race([promise1, promise2]) : Promise
let p = Promise.race([getData1(), getData2(),getData3()]);    p.then (data=>{   console.log(data);  })
let flag = false;  function func(data) {   if(flag) return   flag = true;   console.log(data);  }    function getData1() {   setTimeout(()=>{    console.log("第一條數據加載成功");    func({name: 'da'});   },500);  }    function getData2() {   setTimeout( () => {    console.log("第二條數據加載成功");    func({name: 'dada'});   }, 1000);  }    getData1();  getData2();    第一條數據加載成功  {name: 'da'}  第二條數據加載成功

Promise.resolve與Promise.reject

Promise.resolve() 與 Promise.reject()

// Promise.resolve  傳遞一個普通的值  let p1 = new Promise(resolve => {   resolve('成功!');  });    let p2 = Promise.resolve('成功!');    // 傳遞一個promise實例  let pro = new Promise(resolve => {   resolve('da');  });    let p = Promise.resolve(pro);    p.then(data => void console.log(data));
let obj = {   then (cb) {    console.log('da');    da('dada');   },   method() {    console.log('coding');   }  }    // 立即執行  Promise.resolve(obj).then(data => {   console.log(data);  });

Promise異步:

console.log(1);    let p = new Promise(resolve => {   console.log(2);   resolve();   console.log(3);  });    console.log(4);    p.then(()=>{   console.log(5);  });    console.log(6);    // 123465

Promise改善了傳統回調造成的代碼難維護,控制反轉等問題,promise是異步的,如果all接收的是空數組,馬上會被決議為成功,如果race接受的是空數組,那麼會被永遠掛起,無限捕獲錯誤問題。

resove和reject方法:

如果接收的是普通的值,那麼就會立即決議為成功,並填充這個值,如果接收的是一個promise實例,那麼返回這個promise實例,如果接收的是個thenable對象,則會把它包裝成promise對象,並立即執行這個對象的then方法,reject會產生一個決議失敗的promise並直接傳遞值。

JavaScript/ES6 Promise

JavaScript的Promise代表一個操作的結果還沒有結果,就是如網絡請求操作,當我們從某個數據源獲取數據的時候,沒有辦法確定它什麼時候能夠返回,接受到響應。

Promise提供標準

doSomething()   .then(doSomethingElse)   .catch(handleError)   .then(doMore)   .then(doFinally)   .catch(handleAnotherError)

創建Promise

一個Promise使用Promise構造器創建,接受兩個參數resolve,reject

var promise = new Promise( function(resolve, reject) {   // new Promise resolve() reject()  }
XMLHttpRequest的promise版本:    function get(url) {   return new Promise(function(resolve, reject) {    var req = new XMLHttpRequest();    req.open('GET', url);    req.onload = function() {     if(req.status == 200) {      resolve(req.response);     }else{      reject(Error(req.statusText));     }    };    req.onerror = function() {     reject(Error("Network Error"));    };     req.send();    });   }

使用Promise

get(url)   .then(function(response) {   },function(err) {   })

處理錯誤:

get(url)   .then(function(response){   }, undefined)   .then(undefined, function(err){   })
get(url)   .then(function(response){   })   .catch(function(err){   })

鏈式

get(url)   .then(function(response){    response = JSON.parse(response);    var secondURL = response.data.url    return get(secondURL);   })   .then(function(response){    response = JSON.parse(response);    var thirdURL = response.data.url    return get(thirdURL);   })   .catch(function(err){    handleError(err);   });

並行執行Promise

Promise.all()方法每個promise數組成為則決議為成功,如果數組中有任意一個promise為失敗則決議為失敗。

任務一,任務二,任務三,.then() -> success 任務成功

ES6

Promise對象用於表示一個異步操作的最終狀態,以及其返回的值。

語法:

new Promise(function (resolve, reject) {  });

幾種狀態:

pending初始狀態,既不是成功,也不是失敗狀態;fulfilled意味着操作成功完成;rejected意味着操作失敗。

pending狀態的Promise對象可能會觸發filfilled狀態,並傳遞一個值給響應的狀態處理方法,也可能觸發失敗狀態rejected並傳遞失敗信息。

Promise.all(iterable)

這個方法返回一個新的promise對象,該promise對象在itearable參數中,當裏面所有的的promise對象決議成功的時候才觸發成功,否則裏面如何一個promise對象決議失敗的時候,立即觸發promise對象的失敗。

Promise.all方法常用於處理多個promise對象的狀態集合。

Promise.race(iterable)

當iterable參數里的任意一個子promise被決議成功或者是決議失敗後,父promise會用子promise的成功返回值,或是失敗返回。

Promise.reject(reason)

返回一個狀態為失敗的promise對象,將給定的失敗信息傳遞給對應的處理方法。

Promise.resolve(value)

返回一個狀態為失敗的promise對象,將給定的失敗信息傳遞給對應的處理方法。

const myPromise = new Promise( (resolve, reject) => {   resolve('resolve'); // filfilled   reject('reject'); // rejected  });
function myFunction(url) {   return new Promise( (resolve, reject) => {    xhr.open ("GET", url);    xhr.onload = () => resolve(xhr.responseText);    xhr.onerror = () => reject(xhr.statusText);    xhr.send();   });  };

當異步代碼執行成功的時候,會調用resolve(),當異步代碼執行失敗的時候,會調用reject()。

模擬異步代碼:

setTimeout(function(){   resolve('成功');  },250);  });    myPromise.then(function(successMsg){  });

ES6 Promise對象

Promise對象是異步編程的一種解決方案,語法上,Promise是一個對象,從它那可以獲取異步操作的信息。

Promise的狀態,promise異步操作有三種狀態,pending(進行中),fulfilled(已成功),reject(已失敗)。除了異步操作的結果,任何其他操作都是無法改變這個狀態。

const p1 = new Promise(function(resolve,reject){      resolve('success1');      resolve('success2');  });    const p2 = new Promise(function(resolve,reject){      resolve('success3');      reject('reject');  });    p1.then(function(value){      console.log(value); // success1  });    p2.then(function(value){      console.log(value); // success3  });

缺點,一旦建立Promise就會立即執行,無法中途取消,如果不設置回調函數,Promise內部會拋出錯誤,不會反應到外部。

then方法,接收兩個函數作為參數。

第一個參數是 Promise 執行成功時的回調,第二個參數是 Promise 執行失敗時的回調,兩個函數只會有一個被調用。

const p = new Promise(function(resolve,reject){    resolve('success');  });    p.then(function(value){    console.log(value);  });    console.log('first');  // first  // success
const p = new Promise(function(resolve,reject){    resolve(1);  }).then(function(value){ // 第一個then // 1    console.log(value);    return value * 2;  }).then(function(value){ // 第二個then // 2    console.log(value);  }).then(function(value){ // 第三個then // undefined    console.log(value);    return Promise.resolve('resolve');  }).then(function(value){ // 第四個then // resolve    console.log(value);    return Promise.reject('reject');  }).then(function(value){ // 第五個then //reject:reject    console.log('resolve:' + value);  }, function(err) {    console.log('reject:' + err);  });

then方法將返回一個resolved或是rejected狀態的Promise對象用於鏈式調用。

熱Promise

在JavaScript中,所有代碼都是單線程的,也就是同步執行的,promise就是為了提供一個解決方案的異步編程。

promise的特點:只有異步操作可以決定當前處於的狀態,並且任何其他操作無法改變這個狀態;一旦狀態改變,就不會在變。

狀態改變的過程:從pending變為fulfilled和從pending變為rejected,狀態改變後,就不會在改變了,這就叫已定型resolved

用法:

Promise對象是由關鍵字new及其構造函數來創建的。

const promise = new Promise((resolve, reject) => {      // do something here ...      if (success) {          resolve(value); // fulfilled      } else {          reject(error); // rejected      }  }); 

函數接收兩個函數作為參數,分別是resolve和reject,當異步操作執行成功後,會將異步操作的結果作為參數傳入resolve函數並執行,此時的狀態由Promise狀態從pending變為fulfilled;而失敗會將異步操作的錯誤作為參數傳入reject函數並執行,此時Promise對象狀態從pending變為rejected。

通過then方法,將指定resolved狀態和rejected狀態的回調函數。

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

Promise.all(iterable),iterable必須是一個可以迭代的對象,如Array

返回值為一個新的Promise實例。

var p1 = new Promise((resolve, reject) => {    setTimeout(resolve, 1000, 'one');  });  var p2 = new Promise((resolve, reject) => {    setTimeout(resolve, 2000, 'two');  });  var p3 = new Promise((resolve, reject) => {    setTimeout(resolve, 3000, 'three');  });  var p4 = new Promise((resolve, reject) => {    reject('p4 reject!');  });  var p5 = new Promise((resolve, reject) => {    reject('p5 reject!');  });    Promise.all([p1, p2, p3, p4, p5]).then(values => {    console.log(values);  }, reason => {    console.log(reason)  });    // p4 reject!

Promise.race(iterable),同理,返回值為一個新的Promise實例。

返回的新實例狀態,會是最先改變狀態的那個實例,如果不是Promise實例,先用Promise.resolve方法,如果傳入的迭代為空,則返回的Promise永久等待。

一個Promise實例 原封不動的返回該實例;

var original = Promise.resolve('第二行');    var da = Promise.resolve(original);    da.then(function(value) {    console.log('value: ' + value);  });    console.log('original === da ? ' + (original === da));    // "original === da ? true"  // "value: 第二行"

跟隨這個thenable對象的,採用它的最終狀態;

let thenable = {    then: function(resolve, reject) {      resolve(41);    }  }    let p = Promise.resolve(thenable);    p.then(function(value) {    console.log(value);  })     // 41

直接將傳入參數當最終結果,並返回一個新的Promise;

let p = Promsie.resolve(12);    p.then(function(number) {    console.log(number);  })    // 12

直接返回一個resolved狀態的Promise對象

let p = Promsie.resovle();    p.then(function() {    // do something  })

Promise.prototype.then()

p.then(onResolve, onReject);    p.then(function(value) {    }, function(reason) {  });

Promise.prototype. catch()

p.catch(onReject)     p.catch(function(reason) {   });
  // bad  promise    .then(function(data) {      // success    }, function(err) {      // error    });    // good  promise    .then(function(data) {      // success    })    .catch(function(err) {      // error    });

Promise.prototype. finally()

  p.finally(onFinally);      p.finally(function() {      })

該回調函數的不接受任何參數

promise是一個對象,代表一個異步操作,有三種狀態,進行中,成功,失敗。只有異步操作的結果的可以決定當前是哪種狀態,promise一旦新建執行,就沒有辦法中途停止。

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。只有當作為參數所有的promise函數運行完畢,才會執行.then回調。

file

file

file

//以往回調方式  函數1(function(){      //代碼執行...(ajax1)        函數2(function(){          //代碼執行...(ajax2)            函數3(function(data3){                //代碼執行...(ajax3)          });          ...      });  });    //Promise回調方式:鏈式調用,可構建多個回調函數。  promise().then().then()...catch()
//創建Promise實例  let promise = new Promise( (resolve, reject) => {      //執行相應代碼 根據情況調用resolve或reject      ...  })    //promise的then方法中執行回調  promise.then(function(){          //第一個參數是返回resolve時      },function(){          //第二個是回調返回reject時      }  }

定時器調用

const promise = new Promise(function(resolve, reject){      setTimeout(resolve,1000);  })    promise.then(function(){    console.log('resolve:成功回調函數')  },function(){    console.log('reject:失敗回調函數')  })

file

傳遞參數:

const promise = new Promise((resolve, reject) => {      setTimeout(reject,1000,'我是value值');  })    promise.then((value) => {      console.log('resolve:' + value)  }).catch((value) => {      console.log('reject:'+ value)  })    //第一種,單個傳值是無效的  const promise = new Promise((resolve, reject) => {      setTimeout(resolve,1000,'參數1','參數2');  })    promise.then((value1,value2) => {      console.log('value1:' + value1)     //value1:參數1      console.log('value2:' + value2)     //value2:undefined  }).catch((value) => {      console.log(value)  })    //第二種:數組傳值  const promise = new Promise((resolve, reject) => {      setTimeout(resolve,1000,['參數1','參數2']);  })  promise.then((value1) => {      console.log('value1:' + value1)     //value1:參數1,參數2  }).catch((value) => {      console.log(value)  })

Promise.prototype.then方法:鏈式操作

getJSON("/posts.json").then(function(json) {    return json.post;  }).then(function(post) {    // proceed  });

Promise.prototype.catch方法:捕捉錯誤

getJSON("/posts.json").then(function(posts) {  }).catch(function(error) {    console.log('發生錯誤!', error);  });

Promise.all方法,Promise.race方法

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