JavaScript詳解使用Promise處理回調地獄與async await修飾符
Promise
Promise能夠處理異步程序。
回調地獄
JS中或node中,都大量的使用瞭回調函數進行異步操作,而異步操作什麼時候返回結果是不可控的,如果我們希望幾個異步請求按照順序來執行,那麼就需要將這些異步操作嵌套起來,嵌套的層數特別多,就會形成回調地獄 或者叫做 橫向金字塔。
案例:有a.txt、b.txt、c.txt三個文件,使用fs模板按照順序來讀取裡面的內容,代碼:
// 將讀取的a、b、c裡面的內容,按照順序輸出 const fs = require('fs'); // 讀取a文件 fs.readFile('./a.txt','utf-8',(err,data)=>{ if(err) throw err; console.log(data); // 讀取b文件 fs.readFile('./b.txt','utf-8',(err,data)=>{ if(err) throw err; console.log(data); // 讀取c文件 fs.readFile('./c.txt','utf-8',(err,data)=>{ if(err) throw err; console.log(data) }) }) })
案例中,循環嵌套的代碼越來越多,這就是 地獄回調
Promise簡介
- Promise對象可以解決 回調地獄 的問題
- Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合理更強大
- Promise可以理解為是一個容器,裡面可以編寫異步程序的代碼
- 從語法上說,Promise是一個對象,使用的時候需要 new
Promise簡單使用
Promise是“承諾”的意思,實例中,它裡面的異步操作就相當於一個承諾,而承諾就會有兩種結果,要麼完成瞭承諾的內容,要麼失敗。
所以,使用Promise,分為兩大部分,首先是有一個承諾(異步操作),然後再兌現結果。
第一部分:定義"承諾"
// 實例化一個Promise,表示定義一個容器,需要給它傳遞一個函數作為參數,而該函數又有兩個形參,通常用resolve和reject表示。該函數裡面可以寫異步請求的代碼 // 換個角度,也可以理解為定下瞭一個承諾諾 let p = new Promise((resolve,reject)=>{ // 形參resolve,單詞意思是 完成 // 實參reject,單詞意思是 失敗 fs.readFile('./a.txt','utf-8',(err,data)=>{ if(err){ // 失敗,就告訴別人,承諾失敗瞭 reject(err); }else{ // 成功,就告訴別人,承諾實現瞭 resolve(data); } } })
第二部分:獲取"承諾"的結果
// 通過調用 p 的then方法,可以獲取到上述 "承諾" 的結果 // then方法有兩個函數類型的參數,參數1表示承諾成功時調用的函數,參數2可選,表示承諾失敗時調用的函數 // p.then( // (data)=>{}, // 函數類型的參數,用於獲取承諾成功後的數據 // (err)=>{} // 函數類型的參數,用於承諾失敗後的錯誤信息 //) p.then( (data)=>{ console.log(data); }, (err)=>{ console.log(err); } )
三種狀態
- 最初狀態:pending,等待中,此時的promise結果為undefined
- 當resolve (value) 調用時,達到最終狀態之一:fulfilled,(成功的)完成,此時可以獲取結果value
- 當reject (error) 調用時,達到最終狀態之一:rejected,失敗,此時可以獲取錯誤信息error
當達到最終的fulfilled 或 rejected 時,promise的狀態就不會再改變瞭。
特點
當調用 resolve的時候,Promise 將到達最終的狀態。 達到最終狀態之後,Promise的狀態就不會再改變瞭。
多次調用resolve函數,隻有第一次有效,其他的調用都無效。
const fs = require('fs'); // 1.創建Promise對象:(承諾) let p = new Promise((resolve,reject)=>{ resolve(123); resolve(456); // 這次的調用無效 }); // 2.獲取異步任務的結果 // p.then(函數1,[函數2]); p.then(res =>{ console.log(res); // 123 },err =>{ console.log(err); })
then方法的鏈式調用
- 前一個then裡面返回的字符串,會被下一個then方法接收到。但是沒有意義;
- 前一個then裡面返回的Promise對象,並且調用resolve的時候傳遞瞭數據,數據會被下一個then接收到
- 前一個then裡面如果沒有調用resolve,則後續的then不會接收到任何值
const fs = require('fs'); // promise 承諾 let p1 = new Promise((resolve, reject) => { fs.readFile('./a.txt', 'utf-8', (err, data) => { err ? reject(err) : resolve(data.length); }); }); let p2 = new Promise((resolve, reject) => { fs.readFile('./b.txt', 'utf-8', (err, data) => { err ? reject(err) : resolve(data.length); }); }); let p3 = new Promise((resolve, reject) => { fs.readFile('./c.txt', 'utf-8', (err, data) => { err ? reject(err) : resolve(data.length); }); }); p1.then(a => { console.log(a); return p2; }).then(b => { console.log(b); // 接收上面傳遞的值p2 return p3; }).then(c => { console.log(c) // 接收上面傳遞的值p3 }).catch((err) => { console.log(err); // 如果錯誤,打印報錯信息 });
catch方法可以統一獲取到 錯誤信息
封裝按順序異步讀取文件的函數
function myReadFile(path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf-8', (err, data) => { err ? reject(err) : resolve(data.length); }) }); } myReadFile('./a.txt') .then(a => { console.log(a); return myReadFile('./b.txt'); }) .then(b => { console.log(b); return myReadFile('./c.txt'); }) .then(c => { console.log(c) }) .catch((err) => { console.log(err); });
使用第三方模塊讀取文件
- npm init – y
- npm i then-fs 安裝then-fs模塊
- then-fs 將 內置的fs模塊封裝瞭,讀取文件後,返回 Promise 對象,省去瞭我們自己封裝
// then-fs 模塊是第三方模塊,需要 npm install then-fs 下載安裝的 const fs = require('then-fs'); // then-fs 對內置的fs模塊進行瞭重寫的封裝。調用方法後,返回Promise對象 let p1 = fs.readFile('./files/a.txt', 'utf-8'); // let p2 = fs.readFile('./files/b.txt', 'utf-8'); let p3 = fs.readFile('./files/c.txt', 'utf-8'); // 通過then獲取結果 p1.then(res => { console.log(res.length); return p2; }).then(res => { console.log(res.length); return p3; }).then(res => { console.log(res.length); })
async和await 修飾符
ES6 — ES2015
async 和 await 是 ES2017 中提出來的。
異步操作是 JavaScript 編程的麻煩事,麻煩到一直有人提出各種各樣的方案,試圖解決這個問題。
從最早的回調函數,到 Promise 對象,再到 Generator 函數,每次都有所改進,但又讓人覺得不徹底。它們都有額外的復雜性,都需要理解抽象的底層運行機制。
異步I/O不就是讀取一個文件嗎,幹嘛要搞得這麼復雜?異步編程的最高境界,就是根本不用關心它是不是異步。
async 函數就是隧道盡頭的亮光,很多人認為它是異步操作的終極解決方案。
ES2017 提供瞭async和await關鍵字。await和async關鍵詞能夠將異步請求的結果以返回值的方式返回給我們。
async 用於修飾一個 function
- async 修飾的函數,總是返回一個 Promise 對象
- 函數內的返回值,將自動包裝在 resolved 的 promise 中
await 隻能出現在 async 函數內
- await 讓 JS 引擎等待直到promise完成並返回結果
- 語法:let value = await promise對象; // 要先等待promise對象執行完畢,才能得到結果
- 由於await需要等待promise執行完畢,所以await會暫停函數的執行,但不會影響其他同步任務
對於錯誤處理,可以選擇在async函數後面使用 .catch() 或 在promise對象後使用 .catch()
const fs = require('fs'); // 將異步讀取文件的代碼封裝 function myReadFile (path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf-8', (err, data) => { err ? reject(err) : resolve(data.length); }); }).catch(err => { console.log(err); }); } async function abc () { let a = await myReadFile('./a.txt'); let b = await myReadFile('./b.txt'); let c = await myReadFile('./c.txt'); console.log(b); console.log(a); console.log(c); } abc();
錯誤處理
前提是得到幾個Promise對象,代碼如下:
let fs = require('fs'); let p1 = new Promise('./files/a.txt','utf-8'); let p2 = new Promise('./files/bbb.txt','utf-8'); // 註意:這裡故意寫錯路徑 let p3 = new Promise('./files/c.txt','utf-8');
獲取Promise的結果,可以通過then獲取。也可以通過async和await獲取。
如果使用then獲取結果,那麼錯誤如何處理?在鏈式調用的尾端,加一個catch方法即可
// ----------------------------通過 then 獲取結果----------------------------- p1.then(res=>{ console.log(res); return p2; }).then(res=>{ console.log(res); return p3 }).catch(err=>{ console.log(err); })
如何使用async和await獲取結果,最好的錯誤處理方案,就是使用 try...catch...
// ---------------------------通過 async/await 獲取結果-------------------- async function abc(){ try{ // 嘗試做一些事情 let r1 = await p1; // 正常獲取結果 let r2 = await p2; // 這裡出錯瞭,就會拋出錯誤 throw err. let r3 = await p3; console.log(r1,r2,r3) }catch(e){ console.log(e); // catch這裡,會抓住前面try裡面拋出的錯誤 } } abc();
小結
Promise是異步任務的一種解決方案
得到Promise對象
- 自己new Promise()
- 自己封裝函數,函數中返回 Promise對象
- 使用第三方的模塊或者庫。比如 then-fs ,比如 axios
獲取結果
- 通過 then 方法獲取,並且通過 catch 捕獲錯誤信息
- 通過 async和await的配合獲取結果,並且通過 try…catch…捕獲錯誤
到此這篇關於JavaScript詳解使用Promise處理回調地獄與async await修飾符的文章就介紹到這瞭,更多相關JavaScript Promise回調地獄內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 淺析Promise的介紹及基本用法
- 徹底搞懂 javascript的Promise
- vue中Promise的使用方法詳情
- JS異步代碼單元測試之神奇的Promise
- 前端常見面試題之async/await和promise的區別