溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何上手Promise和aysnc/await

發(fā)布時間:2021-09-13 15:38:53 來源:億速云 閱讀:161 作者:柒染 欄目:web開發(fā)

如何上手Promise和aysnc/await,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

單線程,就是指一次只能完成一件任務,如果有多個任務就必須排隊等候,前面一個任務完成,再執(zhí)行后面一個任務。這種“單線程”模式執(zhí)行效率較低,任務耗時長。

為了解決這個問題,就有了異步模式,也叫異步編程。

一、異步編程

所謂"異步",簡單說就是一個任務分成兩段,先執(zhí)行第一段,然后轉而執(zhí)行其他任務,當第一段有了執(zhí)行結果之后,再回過頭執(zhí)行第二段。

JavaScript采用異步編程原因有兩點:

  • JavaScript是單線程。

  • 為了提高CPU的利用率。

在提高CPU的利用率的同時也提高了開發(fā)難度,尤其是在代碼的可讀性上。

那么異步存在的場景有:

  • fs 文件操作

require("fs").readFile("./index.html",(err,data)=>{})
  • 數據庫操作

  • AJAX

$.get("/user",(data)=>{})

定時器

setTimeout(()=>{},2000)

二、Promise是什么

Promise理解

(1) 抽象表達

  • Promise 是一門新的技術(es6規(guī)范)

  • Promise是js中進行異步編程的新解決方案

(2) 具體表達

  • 從語法上說:Promise是一個構造函數

  • 從功能上說:Promise對象是用來封裝一個異步操作并可以獲取其成功/失敗的結果值

為什么要使用Promise

(1) 指定回調函數的方式更加靈活

  • promise:啟動異步任務=>返回promise對象=>給promise對象綁定回調函數

(2) 支持鏈式調用方式,可以解決回調地獄問題

  • 什么是回調地獄?

回調地獄就是回調函數嵌套使用,外部回調函數異步執(zhí)行的結果是嵌套的回調執(zhí)行的條件

  • 回調地獄的缺點

不便于閱讀

不便于異常處理

解決方法

Promise的狀態(tài)

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2. Promise必須擁有三種狀態(tài):pending、rejected、resolved

  3. 如果Promise的狀態(tài)是pending時,它可以變成成功fulfilled或失敗rejected

  4. 如果promise是成功狀態(tài),則它不能轉換為任何狀態(tài),而且需要一個成功的值,并且這個值不能改變

  5. 如果promise是失敗狀態(tài),則它不能轉換成任何狀態(tài),而且需要一個失敗的原因,并且這個值不能改變

Promise的狀態(tài)改變

pending未決定的,指的是實例狀態(tài)內置的屬性

(1)pending變?yōu)閞esolved/fullfilled

(2)pending變?yōu)閞ejected

說明:Promise的狀態(tài)改變只有兩種,且一個Promise對象只能改變一次,無論失敗還是成功都會得到一個結果輸出,成功的結果一般是value,失敗的結果一般是reason。

無論狀態(tài)是成功還是失敗,返回的都是promise。

Promise的值

實例對象中的另一個屬性 [PromiseResult]保存著異步任務 [成功/失敗] 的結果resolve/reject。

Promise的api

手寫Promide中的api:

(1)promise構造函數 Promise(executor){}

  • executor:執(zhí)行器(resolve,reject)=>{}

  • resolve:內部定義成功時我們需要調用的函數value=>{}

  • reject:內部定義失敗時我們調用的函數  reason=>{}說明:executor會在Promise內部立即同步調用,異步操作在執(zhí)行器中執(zhí)行

(2)Promise.prototype.then方法:(onResolved,rejected)=>{}

  • onResolved函數:成功的回調函數value=>{}

  • rejected函數:失敗的回調函數reason=>{}

說明:指定用于得到成功value的成功回調和用于得到失敗reason的失敗回調,返回一個新的promise對象

(3)Promise.prototype.catch方法:(onRejected)=>{}

前三條是本文章中將要實現的手寫代碼,當然Promise還有其它的api接口。

(1)Promise.prototype.finally()方法

finally()方法用于指定不管 Promise  對象最后狀態(tài)如何,都會執(zhí)行的操作。不管promise最后的狀態(tài),在執(zhí)行完then或catch指定的回調函數以后,都會執(zhí)行finally方法指定的回調函數。

promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});

(2)Promise.all()方法

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

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

p的狀態(tài)由p1、p2、p3決定,分成兩種情況。

  • 只有p1、p2、p3的狀態(tài)都變成fulfilled,p的狀態(tài)才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

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

(3)Promise.race()方法

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

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

只要p1、p2、p3之中有一個實例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

(4)Promise.allSettled()方法

Promise.allSettled()方法接受一組 Promise 實例作為參數,包裝成一個新的 Promise  實例。只有等到所有這些參數實例都返回結果,不管是fulfilled還是rejected,包裝實例才會結束。

const promises = [   fetch('/api-1'),   fetch('/api-2'),   fetch('/api-3'), ];  await Promise.allSettled(promises); removeLoadingIndicator();

該方法返回的新的 Promise 實例,一旦結束,狀態(tài)總是fulfilled,不會變成rejected。狀態(tài)變成fulfilled后,Promise  的監(jiān)聽函數接收到的參數是一個數組,每個成員對應一個傳入Promise.allSettled()的 Promise 實例。

(5)Promise.any()方法

ES2021 引入了Promise.any()方法。該方法接受一組 Promise 實例作為參數,包裝成一個新的 Promise  實例返回。只要參數實例有一個變成fulfilled狀態(tài),包裝實例就會變成fulfilled狀態(tài);如果所有參數實例都變成rejected狀態(tài),包裝實例就會變成rejected狀態(tài)。

(6)Promise.reject(reason)方法

Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態(tài)為rejected。

const p = Promise.reject('出錯了'); // 等同于 const p = new Promise((resolve, reject) => reject('出錯了'))  p.then(null, function (s) {   console.log(s) }); // 出錯了

(7)Promise.resolve()方法 有時需要將現有對象轉為 Promise 對象,Promise.resolve()方法就起到這個作用。

Promise.resolve('foo') // 等價于 new Promise(resolve => resolve('foo'))

改變promsie狀態(tài)和指定回調函數誰先誰后?

(1)都有可能,正常情況下是先指定回調函數再改變狀態(tài),但也可以先改變狀態(tài)再指定回調函數。

(2)如何改變狀態(tài)再指定回調?

  • 在執(zhí)行器中直接調用resolve/reject

  • 延遲更長時間才進行調用then

(3)什么時候才能得到數據?

  • 如果先指定的回調,那當狀態(tài)發(fā)生改變,回調函數就會調用,得到數據

  • 如果先改變的狀態(tài),那當指定回調時,回調函數就會進行調用,得到數據

示例:

let p = new Promise((resolve,reject)=>{     resolve("成功了");     reject("失敗了"); });  p.then((value)=>{     console.log(value); },(reason)=>{     console.log(reason); })

Promise規(guī)范

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2. "Promise"是一個具有then方法的對象或函數,其行為符合此規(guī)范。也就是說Promise是一個對象或函數

  3. "thenable"是一個具有then方法的對象或函數,也就是這個對象必須擁有then方法

  4. "value"是任何合法的js值(包括undefined或promise)

  5. promise中的異常需要使用throw語句進行拋出

  6. promise失敗的時候需要給出失敗的原因

then方法說明

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2. 一個promise必須要有一個then方法,而且可以訪問promise最終的結果,成功或者失敗的值

  3. then方法需要接收兩個參數,onfulfilled和onrejected這兩個參數是可選參數

  4. promise無論then方法是否執(zhí)行完畢,只要promise狀態(tài)變了,then中綁定的函數就會執(zhí)行。

鏈式調用

Promise最大的優(yōu)點就是可以進行鏈式調用,如果一個then方法返回一個普通值,這個值就會傳遞給下一次的then中,作為成功的結果。

如果返回的是一個promise,則會把promise的執(zhí)行結果傳遞下去取決于這個promise的成功或失敗。

如果返回的是一個報錯,就會執(zhí)行到下一個then的失敗函數中。

三、手寫Promise代碼

面試經??嫉氖謱慞romise代碼,可以仔細理解一下。

// 手寫Promise // 首先定義一個構造函數,在創(chuàng)建Promise對象的時候會傳遞一個函數executor, // 這個函數會立即被調用,所以我們在Promise內部立即執(zhí)行這個函數。 function Promise(executor){     // 用于保存promise的狀態(tài)     this.status = "pending";     this.value;//初始值     this.reason;//初始原因     this.onResolvedCallbacks = [];//存放所有成功的回調函數     this.onRejectedCallbacks = [];//存放所有失敗的回調函數     //定義resolve函數     const resolve = (value)=>{         if(this.status === "pending"){             this.status = "resolved";             this.value = value;             this.onResolvedCallbacks.forEach(function(fn){                 fn()             })         }     }     //定義reject函數     const reject = (reason)=>{         if(this.status === "pending"){             this.status = "rejected";             this.reason = reason;             this.onRejectedCallbacks.forEach(function(fn){                 fn()             })         }     }     executor(resolve,reject); }  Promise.prototype.then = function(onFulfilled,onRejected){ /* 每次then都會返回一個新的promise  我們需要拿到當前then方法執(zhí)行成功或失敗的結果, 前一個then方法的返回值會傳遞給下一個then方法, 所以這里我們要關心onFulfilled(self.value) 和 onRejected(self.reason)的返回值,我們這里定義一個x來接收一下。  如果失敗拋錯需要執(zhí)行reject方法,這里使用try...catch捕獲一下錯誤。 也就是判斷then函數的執(zhí)行結果和返回的promise的關系。 */     return new Promise((resolve,reject)=>{         //當Promise狀態(tài)為resolved時         if(this.status === "resolved"){             try{                 resolve(onFulfilled(this.value))             }catch(error){                 reject(error)             }         }         //當Promise狀態(tài)為rejected時         if(this.status === "rejected"){             try {                 resolve(onRejected(this.reason))             } catch (error) {                 reject(error)             }         }         //當Promise狀態(tài)為pendding         if(this.status === "pending"){             this.onResolvedCallbacks.push(function(){                 try{                     resolve(onFulfilled(this.value))                 }catch(error){                     reject(error)                 }             });             this.onRejectedCallbacks.push(function(){                 try {                     resolve(onRejected(this.reason))                 } catch (error) {                     reject(error)                 }             });         }     }) }

升級版Promise:

class Promise{     /*首先定義一個構造函數,在創(chuàng)建Promise對象的時候會傳遞一個函數executor,     這個函數會立即被調用,所以我們在Promise內部立即執(zhí)行這個函數。*/     constructor(executor){         this.executor = executor(this.resolve,this.reject);                  this.onResolvedCallbacks = [];//存放所有成功的回調函數         this.onRejectedCallbakcs = [];//存放所有失敗的回調函數     }           // 用于存儲相應的狀態(tài)     status = "pending";     // 初始值     value;     // 初始原因     reason;          // executor在執(zhí)行的時候會傳入兩個方法,一個是resolve,     // 一個reject,所以我們要創(chuàng)建這兩個函數,而且需要把這兩個函數傳遞給executor。     // 當我們成功或者失敗的時候,執(zhí)行onFulfilled和onRejected的函數,     // 也就是在resolve函數中和reject函數中分別循環(huán)執(zhí)行對應的數組中的函數。     // 定義成功事件     resolve(value){         if(status === "pending"){             status = "resolved";             value = value;             this.onResolvedCallbacks.forEach(fn=>{fn()})         }     }     // 定義失敗事件     reject(){         if(this.status === "pending"){             this.status = "rejected";             this.reason = reason;             this.onRejectedCallbakcs.forEach(fn=>{fn()});         }     }     // 這個時候當我們異步執(zhí)行resolve方法時候,then中綁定的函數就會執(zhí)行,并且綁定多個then的時候,多個方法都會執(zhí)行。       // Promise的對象存在一個then方法,這個then方法里面會有兩個參數,一個是成功的回調onFulfilled,     // 另一個是失敗的回調onRejected,只要我們調用了resolve就會執(zhí)行onFulfilled,調用了reject就會執(zhí)行onRejected。     // 為了保證this不錯亂,我們定義一個self存儲this。當我們調用了resolve或reject的時候,需要讓狀態(tài)發(fā)生改變.     // 需要注意的是Promise的狀態(tài)只可改變一次,所以我們要判斷,只有當狀態(tài)未發(fā)生改變時,才去改變狀態(tài)。     then(onFulfilled,onRejected){         // 判斷當前狀態(tài)進行回調         if(this.status === "resolved"){             onFulfilled(self.value)         };         if(this.status === "rejected"){             onRejected(self.reason)         }         // 當狀態(tài)還處于pending狀態(tài)時         // 因為onFulfilled和onRejected在執(zhí)行的時候需要傳入對應的value值,所我們這里用一個函數包裹起來,將對應的值也傳入進去。         if(this.status === "pending"){             this.onResolvedCallbacks.push(()=>{onFulfilled(this.value)});             this.onResolvedCallbacks.push(()=>{onRejected(this.reason)});         }     }  }

使用自己手寫的Promise源碼:

let p = new Promise((resolve,reject)=>{     setTimeout(()=>{         resolve("成功了")     },1000) });  p.then(function(value){     return 123; }).then(value=>{     console.log("收到了成功的消息:",value); }).catch(error=>{     console.log(error); });  p.then(value=>{     console.log(value); })

四、Async/Await

async用來表示函數是異步的,定義的async函數返回值是一個promise對象,可以使用then方法添加回調函數。

await 可以理解為是 async wait 的簡寫。await 必須出現在 async  函數內部,不能單獨使用。函數中只要使用await,則當前函數必須使用async修飾。

所以回調函數的終結者就是async/await。

async命令

  • async函數返回的是一個promise對象。

  • async函數內部return語句返回的值,會成為then方法回調的參數。

  • async函數內部拋出錯誤,會導致返回的 Promise 對象變?yōu)閞eject狀態(tài)。

  • 拋出的錯誤對象會被catch方法回調函數接收到。

async函數返回的 Promise 對象,必須等到內部所有await命令后面的 Promise  對象執(zhí)行完,才會發(fā)生狀態(tài)改變,除非遇到return語句或者拋出錯誤。

也就是說,只有async函數內部的異步操作執(zhí)行完,才會執(zhí)行then方法指定的回調函數。

async function fun(){     // return "hello wenbo";     throw new Error("ERROR"); } fun().then(v => console.log(v),reason=>console.log(reason));//Error: ERROR```

await命令

正常情況下,await命令后面是一個 Promise 對象,返回該對象的結果。如果不是 Promise 對象,就直接返回對應的值。

async function fun(){     return await "zhaoshun";     // 等價于 return "zhaoshun"; } fun().then(value=>console.log(value));//zhaoshun

另一種情況是,await命令后面是一個thenable對象(即定義了then方法的對象),那么await會將其等同于 Promise 對象。

class Sleep{     constructor(timeout){         this.timeout = timeout;     }     then(resolve,reject){         const startTime = Date.now();         setTimeout(()=>resolve(Date.now() - startTime),this.timeout);               }  }  (     async ()=>{         const sleepTime = await new Sleep(1000);         console.log(sleepTime);//1012     } )()   // js里面沒有休眠的語法,但是借助await命令可以讓程序停頓的時間 const sleepFun = (interval) => {     return new Promise(resolve=>{         setTimeout(resolve,interval);     }) }  // 用法 const asyncFun = async ()=>{     for(let i = 1; i <= 5; i++){         console.log(i);         await sleepFun(1000);     } } asyncFun();

從上面可以看到,await命令后面是一個Sleep對象的實例。這個實例不是 Promise  對象,但是因為定義了then方法,await會將其視為Promise處理。

await命令后面的 Promise 對象如果變?yōu)閞eject狀態(tài),則reject的參數會被catch方法的回調函數接收到。

注意:上面代碼中,await語句前面沒有return,但是reject方法的參數依然傳入了catch方法的回調函數。這里如果在await前面加上return,效果是一樣的。

  • 任何一個await語句后面的 Promise 對象變?yōu)閞eject狀態(tài),那么整個async函數都會中斷執(zhí)行。

  • 使用try/catch可以很好處理前面await中斷,而后面不執(zhí)行的情況。

示例:

const fun = async ()=>{     try {         await Promise.reject("ERROR");     } catch (error) {               }     return await Promise.resolve("success");  } fun().then(     value=>console.log(value),reason=>console.log(reason,"error")// ).catch(     error=>console.log(error)//ERROR );

另一種方法是await后面的 Promise 對象再跟一個catch方法,處理前面可能出現的錯誤。

const fun = async ()=>{     await Promise.reject("error").catch(e=>console.log(e));     return await Promise.resolve("success"); } fun().then(v=>console.log(v));//success

錯誤處理

第一點:如果await后面的異步操作出錯,那么等同于async函數后面的promise對象被reject。

const fun = async()=>{     await new Promise((resolve,reject)=>{         throw new Error("error")     }) } fun().then(v=>console.log(v)).catch(e=>console.log(e));

第二點:多個await命令后面的異步操作,如果不存在繼發(fā)關系,最好讓他們同時進行觸發(fā)。

const [fun1,fun2] = await Promise.all([getFun(),getFoo()]);  const fooPromise = getFoo(); const funPromise = getFun(); const fun1 = await fooPromise(); const fun2 = await funPromise();

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

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

第四點:async 函數可以保留運行堆棧。

看完上述內容,你們掌握如何上手Promise和aysnc/await的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI