Promise基礎知識

一. Promise的含義

  • Promise是異步編程的一種解決方案,比傳統的解決方案:回調函數和事件更加合理和強大
  • Promise其實就是一個容器,裏面保存着將來纔會結束的某個事件(一般是異步操做)
  • Promise對象有如下兩個特色:
  1. 對象的狀態不受外界影響。Promise對象表明的是異步操做,有三種狀態,pending進行中,fulfilled已成功,rejected已失敗
  • 只有異步操做的結果才能改變狀態,這也是Promise承諾,名字的由來
  1. 一旦狀態改變就不會再改變,任什麼時候候均可以獲得這個結果。
  • Promise對象的狀態改變狀況:(1) pending改變爲fulfilled;pending改變爲rejected;
  • 只要改變爲fulfilled或者rejected,那麼狀態就凝固了,不會再改變,此時被稱爲resolved已定型
  • 可是通常resolved都是特指fulfilled狀態,而不包括rejected狀態
  1. Promise的一些缺點: (1) 沒法取消Promise,一旦新建就會當即執行,沒法中途取消 (2) 若是不設置回調函數,那麼Promise內部拋出的錯誤不會反映到外部 (3) 當處於pending狀態時,沒法得知目前發展到哪一個階段剛剛開始仍是即將完成

二. 基本用法

1. 基本含義

  • ES6規定,Promise對象是一個構造函數,能夠用來生成Promise實例,new Promise()的參數函數中有兩個參數,調用第一個參數函數會改變狀態爲resolved狀態,調用第二個會改變狀態爲rejected狀態
  • Promise實例生成以後,可使用then方法指定resolved,rejected狀態的回調函數,第一個參數函數在resolved狀態時調用,第二個參數在rejected狀態時調用
  • 下面是一個Promise實例:
var p=new Promise(function(res,rej){
 console.log("剛剛new Promise()就會執行,即便是賦值操做")  // res();// 狀態改成resolved  rej();// 狀態改成rejected  })  // 獲取狀態  p.then(()=>{  console.log('狀態改變爲resolved')  },()=>{  console.log('狀態改變爲rejected')  }) 複製代碼

2. setTimeout和promise實例

function pro(time){
 return new Promise((res,rej)=>{  // new Promise以後就執行,res會在time時間後執行,從而狀態改變爲resolved  setTimeout(res,time)  })  }  console.log(Math.floor(new Date().getTime()/1000))  pro(1000).then(()=>{  console.log(Math.floor(new Date().getTime()/1000))  console.log("在1000ms後執行")  }) 複製代碼

3. Promise異步結合同步實例

let p=new Promise(function(res,rej){
 console.log("new Promise代碼執行,會當即執行該步驟")  console.log(1);  res()  })  p.then(()=>{  console.log("then操做時異步操做,會在本輪宏任務結束以前才執行")  console.log(3)  })  console.log(2); 複製代碼

4. ajax請求實例

var get=function(url){
 return new Promise((res,rej)=>{  var xhr=new XMLHttpRequest();  xhr.open("GET",url);  xhr.onreadystatechange=function(){  if(this.readyState==4&&this.status==200){  res(this.response)  }  }  xhr.onerror=(err)=>{  rej(err)  }  xhr.send();  })  }  // http://127.0.0.1:8849,此時會報錯(由於本地沒開啓該端口)  get("http://127.0.0.1:8848/MyHxsj/面經.md").then((data)=>{  console.log(data)  },(err)=>{ console.log(err) }) 複製代碼

5. resolve返回一個Promise實例

  • 若是調用resolve函數或者reject函數返回時帶有參數。
  • 那麼若是resolve函數的參數是一個Promise實例時,返回的實例的狀態決定了當前Promise實例的狀態
  • 若是返回的Promise實例的狀態是pending,那麼當前Promise實例會等待pending狀態改變
  • 若是返回的Promise實例的狀態是resolved或者rejected,那麼回調函數就會當即執行
// 1. 返回的Promise實例的狀態爲pending
 var p1=new Promise(function(res,rej){  console.log("pending")  setTimeout(function(){  res("resolved")  },2000)  })  var p2=new Promise(function(res,rej){  res(p1);// resolved狀態,可是傳遞的參數爲Promise實例  })  p2.then((data)=>{  console.log(data)  console.log("在返回的Promise實例p1狀態爲resolved以後執行")  })  /*  pending  // 兩秒以後  resolved  在返回的Promise實例p1狀態爲resolved以後執行  */   // 2. 返回的Promise實例的狀態爲resolved  var p3=new Promise(function(res){  res("p3")  })  var p4=new Promise(function(res,rej){  res(p3)  })  p4.then((res)=>{  console.log(res);//p3,當即執行  }) 複製代碼

6. resolve以後的同步代碼執行

  • promise實例改變狀態爲resolve不會改變同步代碼的執行
// 1. resolved
 new Promise((res,rej)=>{  res(1);  console.log("此前狀態爲resolved,可是依舊會執行本行代碼")  }).then((data)=>{  console.log(data)  })  console.log('宏任務代碼')  /*  此前狀態爲resolved,可是依舊會執行本行代碼  宏任務代碼  1  */   // 2. rejected  new Promise((res,rej)=>{  rej(new Error("錯誤error:"));  console.log("此時狀態爲rejected,本行代碼會執行")  }).then(()=>{},(err)=>{  console.log(err)  })  /*  此時狀態爲rejected,本行代碼會執行  // 而後纔會輪到微任務執行  Error: 錯誤error:  at 平時測試.html:130  at new Promise (<anonymous>)  at 平時測試.html:129  */ 複製代碼

7.改變狀態後即刻結束

// 若是想要在改變狀態爲resolved或者rejected以後
 // 不會繼續執行往下代碼,那麼使用return   new Promise(function(res,rej){  return res(1)  // return rej(1)  console.log("已經return,因此不會繼續執行本行代碼")  }).then((data)=>{  console.log(data)  },(err)=>{  console.log(err)  })  // 結果只會打印: 1 複製代碼

三. then()

  • then方法是定義在原型對象Promise.prototype上的,做用是爲Promise實例添加狀態改變時的回調函數
  • then方法的參數不是函數時會發生穿透。也就是then(1).then((data)=>{}),第二個then得到的參數函數的data就是上次穿透的數據1
  • 若是then方法的參數是函數,那麼第一個參數是resolved函數,第二個參數是rejected函數
  • 若是promise實例存在多個then,那麼下一個then會等待上一個then返回的promise實例狀態改成resolved/rejected纔會執行
// 多個then,須要等待上一個then的狀態改成resolved/rejected
 new Promise((res,rej)=>{  res(1)  }).then((res)=>{  console.log(res);  return new Promise((res,rej)=>{  setTimeout(res,1000);// 一秒後,狀態改成resolve  })   }).then(()=>{  console.log(2)  })  /*  1  // 1秒後  2  */ 複製代碼
  • 若是then返回的不是Promise實例對象,那麼會發生值穿透
new Promise((res,rej)=>{
 res(1)  }).then(2).then((data)=>{  console.log(data);//1  return 3;  }).then((data)=>{  console.log(data)//3  })  /*  res(1),函數res傳遞的是一個number類型的變量  then(2),不是函數,沒有傳遞值,因此無效,僅僅是一個語句 2  // 因此1的值會繼續穿透,then(2)無效  1,第二個then屬於函數對象,因此可以接收到數據1  return 3,傳遞數據3  最後一個then,打印3,接收到上個then函數返回的數據3   */ 複製代碼

四. catch()

1. 基本含義

  • Promise.prototype.catch()方法相等於Promise.prototype.then(null,reject())或者Promise.prototype.then(undefined,reject())
  • 用於指定發生錯誤時的回調函數。在異步操做拋出錯誤時,狀態變爲rejected,就會調用catch方法
  • 另外在then方法指定的回調函數中拋出錯誤,就會被catch方法捕獲
  • 調用完catch方法,狀態會變爲resolved!
// 1. 捕獲Promise錯誤
 new Promise(function(res,rej){  throw new Error("拋出錯誤")  }).catch(function(err){  console.log("err:",err)  })  /*  err: Error: 拋出錯誤  at 平時測試.html:116  at new Promise (<anonymous>)  at 平時測試.html:115  */   // 2. then方法中拋出錯誤  new Promise(function(res,rej){  res(1)  }).then(function(data){  throw new Error("then err")  }).catch(function(err){  console.log(err)  })  /*  Error: then err  at 平時測試.html:131  */ 複製代碼

2. catch()至關於調用reject

  • 在promise的後面使用catch其實至關於在promise內部加上try-catch語句,或者至關於加上一個reject狀態改變(在某個條件符合時)
// 1. catch方法相等於try-catch語句
 new Promise(function(res,rej){  try{  throw new Error("try拋出錯誤")  }catch(e){  console.log(e)  }  })  /*  Error: try拋出錯誤  at 平時測試.html:117  at new Promise (<anonymous>)  at 平時測試.html:115  */   // 2. catch方法還相等於調用reject方法而後在then的第二個參數監聽  new Promise(function(res,rej){  rej("rej err")  }).then(()=>{},(err)=>{  console.log(err)  });//rej err 複製代碼

3.resolve狀態再拋出

  • 因爲catch至關於調用rejected狀態,而且狀態改成rejected後不能夠改成resolve狀態
  • 因此Promise狀態爲resolve時,再拋出錯誤,沒法在catch捕獲到
// 1. res(),resolve狀態後不能夠改成reject狀態
 new Promise(function(res,rej){  res("ok");//狀態爲resolve  throw new Error("此時拋出錯誤沒法被捕獲到")  }).catch(function(err){  console.log(err)  }).then((data)=>{  console.log(data);//ok  }) 複製代碼

4. catch與then第二個參數

  • then處於catch以前,則在catch以前就捕獲了錯誤
new Promise(function(res,rej){
 throw new Error("err")  }).then(()=>{},(err)=>{  console.log("reject:",err)  }).catch(function(data){  console.log('catch:',data)  })  /*  reject: Error: err  at 平時測試.html:116  at new Promise (<anonymous>)  at 平時測試.html:115  */ 複製代碼

5. Promise內部拋出的錯誤不會影響到外部

// 1. new Promise內部會拋出錯誤
 new Promise(function(res,rej){  throw new Error("err")  })  // 2.即便上面拋出了錯誤,剩餘的代碼依舊會執行  // 也就是Promise內部拋出的錯誤不會影響到外部  setTimeout(()=>{  console.log("繼續執行")  },1000)  // 會打印錯誤,以後打印 "繼續執行" 複製代碼

6. try-catch捕獲錯誤

  • try-catch捕獲錯誤分爲:try執行以前,try執行過程當中,try執行以後
// 1.語法錯誤,在執行try代碼以前就拋出錯誤,因此catch沒法捕獲到
 /* try{  a.  }catch(e){  console.log("myerr:",e)  } */  /*  Uncaught SyntaxError: Unexpected token '}'  */   // 2. 在try代碼執行過程當中拋出的錯誤(能夠捕獲到)  /* function add(x){return x+y}; // 錯誤函數  try{  console.log(add(1))  }catch(e){  console.log(e);//ReferenceError: y is not defined  } */   // 3. 在try代碼執行完畢以後拋出的錯誤(不能捕獲) /* try{  setTimeout(function(){  console.log('定時器')  // 此時的錯誤沒有被catch捕獲到  a.b;//Uncaught ReferenceError: a is not defined  },2000)  }catch(e){  console.log("捕獲到的錯誤:",e);  } */   // 4.try/catch沒法捕獲到Promise異常時由於Promise內部就捕獲到異常了,沒有往外拋出異常,因此外部的catch沒法捕獲到異常  try{  new Promise(function(res,rej){  // 當沒有在Promise中設置catch時,提示下面語句  throw new Error("err");//Uncaught SyntaxError: Missing catch or finally after try  }).then(()=>{},(err)=>{  console.log("第二個參數:",err)  /*  第二個參數: Error: err  at 平時測試.html:147  at new Promise (<anonymous>)  at 平時測試.html:145  */  })  .catch(function(err){  console.log("catch:",err)  /* 沒有設置then第二個參數函數時  catch: Error: err  at 平時測試.html:147  at new Promise (<anonymous>)  at 平時測試.html:145  */  })  }catch(e){  console.log("promise:",e);// 沒法捕獲到promise內部錯誤  } 複製代碼

7. catch返回的是Promise實例

  • 因爲catch返回的是Promise實例,因此catch後面接上的then能夠繼續執行
  • catch中也能夠繼續拋出錯誤,而後被後面的then或者catch繼續捕獲
// 1. catch捕獲拋出的錯誤後,then方法能夠繼續執行
 new Promise(function(){  throw new Error("err")  }).catch(function(err){  console.log(err)  }).then(function(){  console.log("catch捕獲錯誤後,還能夠繼續執行then")  })  /*  Error: err  at 平時測試.html:116  at new Promise (<anonymous>)  at 平時測試.html:115   catch捕獲錯誤後,還能夠繼續執行then  */   // 2. catch中也能夠拋出錯誤,而後被下一個catch捕獲  new Promise(function(res,rej){  throw new Error("第一個錯誤")  }).catch(function(err){  console.log(err)  throw new Error("第二個錯誤")  }).catch(function(err){  console.log(err)  })  /*  Error: 第一個錯誤  at 平時測試.html:133  at new Promise (<anonymous>)  at 平時測試.html:132   Error: 第二個錯誤  at 平時測試.html:136  */ 複製代碼

五. finally

  • ES2018才引入的,就是無論promise的狀態是什麼,最後都會執行
  • finally方法的回調函數不能接受任何參數,這就意味着不能知道前面的Promise狀態究竟是fulfilled仍是rejected
  • 因此finally方法裏面的操做,應該是和狀態無關的,不依賴於Promise的執行結果
  • finally方法的本質上就是在promise實例最後加上一個then來接收resolved和rejected兩種狀態,而加上finally的好處就在於只須要寫一個接收函數就好了,不須要知道狀態
  • 而且finally方法老是會返回原來的值
// 1. finally在最後
 new Promise(function(res,rej){  res(1)  }).then((data)=>{  console.log(data);//1  return 2  }).finally(()=>{  console.log(3);//3, finally方法不能接受到參數,因此2接收1不到  })  /*  1  3  */   // 2. finally後面還有then方法  new Promise((res,rej)=>{  res(11)  }).then((data)=>{  console.log(data);//11  return 22;  }).finally(()=>{  console.log(33);//33  }).then((data)=>{  console.log(data);//22,執行上一個被返回的then  })   // 3. finally後面還有then,而且finally返回值  new Promise((res,rej)=>{  res(111)  }).then((data)=>{  console.log(data);//111  return "後續then接收到以前的then,而不會接收到finally return的變量"  }).finally(()=>{  console.log(222);//222  return 333;  }).then((data)=>{  console.log(data);//後續then接收到以前的then,而不會接收到finally return的變量  }) 複製代碼

六. Promise.all()

  • Promise.all()方法用於將多個Promise實例包裝成一個新的Promise實例。
  • Promise.all()接收一個具備Iterator接口的數據做爲參數,關鍵在於成員必須都是Promise實例
  • 成員都變爲resolved以後會返回結果數組;若是有一個成員變爲rejected狀態,那麼返回rejected狀態Promise實例的返回值
// 1. 所有resolved
 var p1=new Promise((res,rej)=>{  res(1);  })  var p2=new Promise((res,rej)=>{  res(2);  })  var p3=new Promise((res,rej)=>{  res(3);  })  // Promise.all()返回的是一個Promise實例  // 該實例的值存儲在內部屬性 [[PromiseValue]]  // 想要獲取,須要經過then方法來保存參數  var res1=Promise.all([p1,p2,p3])  .then((data)=>{  console.log(data);//[1, 2, 3]  return data;  })  console.log(res1);//Promise {<pending>}   // 2. 有一個rejected,其餘的中止執行  var p4=new Promise((res,rej)=>{  console.log("p4")  setTimeout(res(4),5000);  })  // 2.1 錯誤沒被捕獲  var p5=new Promise((res,rej)=>{  setTimeout(rej("error:"+5555),1000)  })   var p6=new Promise((res,rej)=>{  console.log("p6")  res(6)  })  /* var res2=Promise.all([p4,p5,p6]).then((data)=>{  console.log("all:",data);  }) */   // 2.2   /* var p5=new Promise((res,rej)=>{  setTimeout(rej("error:"+5555),1000)  }).catch(function(err){  console.log("err",err);//err error:5555  }).then((res)=>{  return "res"  })  console.log(p5) */   // 2.3 給Promise.all後面添加catch捕獲錯誤  var res2=Promise.all([p4,p5,p6]).then((data)=>{  console.log("all:",data);  }).catch((err)=>{  console.log("all+err",err);//all+err error:5555  })  /*  2.1 沒有給p5設置catch捕獲錯誤,因此最後執行錯誤,Promise.all沒接住錯誤  2.2 給p5設置catch捕獲錯誤,則Promise.all剩餘實例也會執行,可是返回rej的實例的值多是undefined  也就是Promise.all的結果是[4,undefined,6]  2.3 給Promise.all後面添加catch捕獲錯誤,打印all+err error:5555  2.4 若是實例和Promise.all都設置了catch,那麼使用實例的catch來捕獲錯誤,由於實例離得近  */ 複製代碼

多個任務異常

  • 使用promise.all時存在多個異步任務異常,catch只捕獲最先的那個錯誤
function runAsync (x) {
 const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))  return p  }  function runReject (x) {  const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))  return p  }  Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])  .then(res =>console.log(res))  .then(res =>console.log(res),rej=> console.log('error:',rej))  .catch(err =>console.log(err)) 複製代碼
  • 多個異步任務是並行執行的,哪一個先執行完畢不必定
  • 當all方法出現異常時,all以後的then接收的是reject函數,參數就是第一個異常返回的參數
  • 當all方法以後的then不存在reject函數時,則catch接受異常參數
  • 即便不是all以後的第一個then存在reject函數,只要以後的then存在reject函數,就不會輪到catch去接收異常
  • 若是異步任務中存在報錯,那麼res不會執行,rej纔會執行,也就是報錯
  • 若是不存在報錯,那麼就會執行res

七. promise.race()

  • race方法也是接收一組異步任務,而後並行執行異步任務,可是只保留第一個執行結果,剩下的異步任務仍在執行,可是執行結果沒法獲取
  • promise.race雖然只會接收第一個任務的結果,可是其餘異步任務依舊會執行
function runAsync(x) {
 const p = new Promise(r =>  setTimeout(() => r(x, console.log(x)), 1000)  );  return p;  }  function runReject(x) {  const p = new Promise((res, rej) =>  setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)  );  return p;  }  /*  首先打印0,而後返回rej('Error:0')  該rej被Promise.race的catch捕獲,可是剩下的成員依舊執行  因此runAsync還會打印 1 2 3  */  Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])  .then(res =>console.log("result: ", res))  .catch(err =>console.log(err));// Error:0 複製代碼

八. Promise.resolve()

  • 將現有的對象轉換爲resolve狀態的異步對象就使用Promise.resolve()
// 1. 使用Promise.resolve()
 var res1=Promise.resolve("1");  console.log(res1);//Promise {<resolved>: "1"}   // 2. 等同於 new Promise((res,rej)=> res())  var res2=new Promise((res,rej)=>{  res("2")  })  console.log(res2);//Promise {<resolved>: "2"} 複製代碼

四種參數狀況

  1. 參數爲Promise實例,那麼就至關於Promise.resolve啥都沒作,無效
  • 原封不動的返回原來的Promise實例
  1. 參數是一個thenable對象
  • thenable對象指的是具備then方法的對象
  • Promise.resolve方法會把thenable對象的then方法執行,而後resolve方法的函數參數就是then中傳遞的參數
  • 注意thenable對象須要有參數res/rej,而後執行res(xxx)用於傳遞數據
var a={
 then:function(res){  res(444)  }  }  Promise.resolve(a).then((data)=>{  console.log(data);//444  }) 複製代碼
  1. 參數是沒有then方法的對象或者不是對象
  • 此時直接傳遞該參數
// 1. 參數不是對象
 Promise.resolve('hello world').then((data)=>{  console.log(data);//hello world  })   // 2. 參數是對象,可是沒有then方法  Promise.resolve({a:'hello'}).then((data)=>{  console.log(data);//{a: "hello"}  }) 複製代碼
  1. 不帶參數
  • 不帶參數就then接收不到數據,仍是可使用!

九. Promise.reject()

  • Promise.reject()方法會返回一個新的Promise實例,實例狀態爲rejected
  • 與Promise.resolve()不一樣之處在於,reject不管參數爲何,都是直接返回該參數!
  • 注意要使用catch接收,不然會報錯,由於是reject狀態
// 1. thenable對象,直接返回參數
 var obj={  then:function(res,rej){  rej("err")  }  }  Promise.reject(obj).catch((e)=>{  console.log(e);//{then: ƒ}  })   //2. 其餘參數  Promise.reject("he").catch((res)=>{  console.log(res);// he  }) 複製代碼

十. 應用

1. 手寫promise

function mypromise(func){
 var that=this;  // 回調函數集  that.funclist=[];  // resolve方法  function resolve(value){  // 微任務  setTimeout(()=>{  that.data=value  that.funclist.forEach((callback)=>{  callback(value)  })  })  }  // 執行用戶傳入的函數  func(resolve.bind(that))  }  mypromise.prototype.then=function(onResolved){  var that=this;  return new mypromise(resolve => {  that.funclist.push(function(){  var res=onResolved(that.data);  if(res instanceof mypromise){  res.then(resolve)  }else{  resolve(res);  }  })  })  }  const func=resolve => {  setTimeout(()=>{  resolve(1)  },1000)  }  var promise1 = new mypromise(func)  promise1.then(res => {  console.log(res)  return new mypromise(resolve => {  setTimeout(() => {  resolve(2)  }, 500)  })  })  console.log(promise1); 複製代碼

2. 預加載圖片

// 用於預加載圖片
 function loadImg(url){  return new Promise(function(res,rej){  var img=document.createElement('img');  img.src=url; // 設置了src,也就是默認開始請求圖片資源了  img.onload=res;//指向函數res   img.onerror=rej;  })  }  console.log(loadImg("http://yiyeblog.com/imgs/ES6.png")) 複製代碼
  • 能夠在NetWork中看到成功加載圖片

本文使用 mdnice 排版html

相關文章
相關標籤/搜索