Promise和Async-Await的入门教程
1. Promise是什么
1.1 理解
-
抽象表達:
-
Promise 是一門新的技術(ES6 規范)
-
Promise 是 JS 中進行異步編程的新解決方案(舊方案是單純使用回調函數)
-
-
具體表達:
-
從語法上來說: Promise 是一個構造函數
-
從功能上來說: promise 對象用來封裝一個異步操作并可以獲取其成功/失敗的結果值
-
1.2 Promise的狀態改變
Promise一種有三種狀態,為 pending、fulfilled、rejected(未決定,履行,拒絕),同一時間只能存在一種狀態,且狀態一旦改變就不能再變。promise是一個構造函數,promise對象代表一項有兩種可能結果(成功或失敗)的任務,它還持有多個回調,出現不同結果時分別發出相應回調。
1.初始化,狀態:pending ?
2.當調用resolve(成功),狀態:pending=>fulfilled ?
3.當調用reject(失敗),狀態:pending=>rejected
狀態的改變(或者說決議)不可逆,一旦決議就不能再更改。
1.3 為什么要用Promise
1)解決回調地獄
回調地獄:發送多個異步請求時,每個請求之間相互都有關聯,會出現第一個請求成功后再做下一個請求的情況。我們這時候往往會用嵌套的方式來解決這種情況,但是這會形成"回調地獄"。如果處理的異步請求越多,那么回調嵌套的就越深。出現的問題:
1.代碼邏輯順序與執行順序不一致,不利于閱讀與維護。
2.異步操作順序變更時,需要大規模的代碼重構。
3.回調函數基本都是匿名函數,bug追蹤困難。
doSomething(function(result) {doSomethingElse(result, function(newResult) {doThirdThing(newResult, function(finalResult) {console.log('Got the final result: ' + finalResult)}, failureCallback)}, failureCallback) }, failureCallback)Promise可以使用鏈式調用解決回調地獄的問題。
/* 使用 promise 的鏈式調用解決回調地獄 */ doSomething().then(function(result) {return doSomethingElse(result) }) .then(function(newResult) {return doThirdThing(newResult) }) .then(function(finalResult) {console.log('Got the final result: ' + finalResult) }) .catch(failureCallback)2)解決異步
舊的回調函數:必須在啟動異步任務前指定
promise:啟動異步任務 => 返回promie對象 => 給promise對象綁定回調函數(甚至可以在異步任務結束后指定/多個)
1.4 工作流程
?
1.5 代碼初體驗
1)fs讀取文件
const fs = require('fs'); ? //回調函數 形式 // fs.readFile('./resource/content.txt', (err, data) => { // ? ? // 如果出錯 則拋出錯誤 // ? ? if(err) throw err; // ? ? //輸出文件內容 // ? ? console.log(data.toString()); // }); ? //Promise 形式 let p = new Promise((resolve , reject) => {fs.readFile('./resource/content.txt', (err, data) => {//如果出錯if(err) reject(err);//如果成功resolve(data);}); }); ? //調用 then p.then(value=>{console.log(value.toString()); }, reason=>{console.log(reason); });2)封裝fs讀取文件模塊
/*** 封裝一個函數 mineReadFile 讀取文件內容* 參數: path 文件路徑* 返回: promise 對象*/ function mineReadFile(path){return new Promise((resolve, reject) => {//讀取文件require('fs').readFile(path, (err, data) =>{//判斷if(err) reject(err);//成功resolve(data);});}); } ? mineReadFile('./resource/content.txt') .then(value=>{//輸出文件內容console.log(value.toString()); }, reason=>{console.log(reason); });2. Promise API
2.1 構造函數
Promise 構造函數: Promise (excutor) {}
(1) executor 函數: 執行器 (resolve, reject) => {}
(2) resolve 函數: 內部定義成功時我們調用的函數 value => {}
(3) reject 函數: 內部定義失敗時我們調用的函數 reason => {}
說明: executor 會在 Promise 內部立即同步調用,異步操作在執行器中執行
let p = new Promise((resolve, reject) => {// 同步調用console.log(111);//修改 promise 對象的狀態reject('error'); });2.2 then
Promise.prototype.then 方法: (onResolved, onRejected) => {}
(1) onResolved 函數: 成功的回調函數 (value) => {}
(2) onRejected 函數: 失敗的回調函數 (reason) => {}
說明: 指定用于得到成功 value 的成功回調和用于得到失敗 reason 的失敗回調返回一個新的 promise 對象
//調用then方法 p.then(value=>{console.log(value); }, reason=>{console.warn(reason); });2.3 catch
Promise.prototype.catch 方法: (onRejected) => {}
(1) onRejected 函數: 失敗的回調函數 (reason) => {}
說明: then()的語法糖, 相當于: then(undefined, onRejected)
let p = new Promise((resolve, reject) => {reject('error'); }); ? // console.log(222); ? //執行 catch 方法 p.catch(reason => {console.log(reason); });2.4 resolve
Promise.resolve 方法: (value) => {}
(1) value: 成功的數據或 promise 對象
說明: 返回一個成功/失敗的 Promise 對象
如果傳入的參數為 非Promise類型的對象, 則返回的結果為成功Promise對象 如果傳入的參數為 Promise 對象, 則參數的結果決定了 resolve 的結果
let p1 = Promise.resolve(521); // console.log(p1); 成功的Promise對象 //如果傳入的參數為 Promise 對象, 則參數的結果決定了 resolve 的結果 let p2 = Promise.resolve(new Promise((resolve, reject) => {// resolve('OK');reject('Error'); })); // console.log(p2); p2.catch(reason => {console.log(reason); })2.5 reject
Promise.reject 方法: (reason) => {}
(1) reason: 失敗的原因
說明: 返回一個失敗的 Promise 對象
// let p = Promise.reject(521); // p2失敗的結果是一個promise對象 let p2 = Promise.reject(new Promise((resolve, reject) => {resolve('OK'); })); ? console.log(p2);2.6 all
Promise.all 方法: (promises) => {}
(1) Promises: 包含 n 個 promise 的數組
(2) 成功的時候返回的是一個結果數組,而失敗的時候則返回最先被reject失敗狀態的值。
說明: 返回一個新的 Promise, 只有所有的 Promise 都成功才成功, 只要有一個失敗了就直接失敗
Promse.all在處理多個異步處理時非常有用,比如說一個頁面上需要等兩個或多個ajax的數據回來以后才正常顯示,在此之前只顯示loading圖標。
let p1 = new Promise((resolve, reject) => {resolve('OK');})let p2 = Promise.resolve('Success');// let p2 = Promise.reject('Error');let p3 = Promise.resolve('Oh Yeah');Promise.all([p1, p2, p3]).then((result) => {console.log(result) // [ 'OK', 'Success',"Oh Yeah" ]}).catch((error) => {console.log(error) // error})2.7 race
Promise.race 方法: (promises) => {}
(1) Promises: 包含 n 個 Promise 的數組
說明: 返回一個新的 Promise, 第一個完成的 Promise 的結果狀態就是最終的結果狀態
let p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');}, 1000); }) let p2 = Promise.resolve('Success'); let p3 = Promise.resolve('Oh Yeah'); ? //調用 const result = Promise.race([p1, p2, p3]); ? //Promise的結果為 Success,因為p2為第一個完成的promise console.log(result);3. Promise關鍵問題
1)一個 Promise 指定多個成功/失敗回調函數, 都會調用嗎?
當 Promise 改變為對應狀態時都會調用。
let p = new Promise((resolve, reject) => {resolve('OK'); }); ? ///指定回調 - 1 p.then(value => {console.log(value); }); ? //指定回調 - 2 p.then(value => {alert(value); });2)改變 Promise 狀態和指定回調函數誰先誰后?
(1) 都有可能, 正常情況下是先指定回調再改變狀態, 但也可以先改狀態再指定回調
(2) 如何先改狀態再指定回調?
① 在執行器中直接調用 resolve()/reject()
② 延遲更長時間才調用 then()
(3) 什么時候才能得到數據?
① 如果先指定的回調, 那當狀態發生改變時, 回調函數就會調用, 得到數據
② 如果先改變的狀態, 那當指定回調時, 回調函數就會調用, 得到數據
3)then方法的返回結果由什么決定
(1) 簡單表達:
由 then()指定的回調函數執行的結果決定
(2) 詳細表達:
① 如果拋出異常, 新 Promise 變為 rejected, reason 為拋出的異常
② 如果返回的是非 Promise 的任意值, 新 Promise 變為 resolved, value 為返回的值
③ 如果返回的是另一個新 Promise, 此 Promise 的結果就會成為新 Promise 的結果
let p = new Promise((resolve, reject) => {resolve('ok'); }); //執行 then 方法 let result = p.then(value => {// console.log(value);//1. 拋出錯誤// throw '出了問題';//2. 返回結果是非 Promise 類型的對象// return 521;//3. 返回結果是 Promise 對象// return new Promise((resolve, reject) => {// ? ? // resolve('success');// ? ? reject('error');// }); }, reason => {console.warn(reason); }); ? console.log(result);4)如何串聯多個任務
(1) Promise 的 then()返回一個新的 promise, 可以開成 then()的鏈式調用
(2) 通過 then 的鏈式調用串連多個同步/異步任務
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');}, 1000); }); ? p.then(value => {return new Promise((resolve, reject) => {resolve("success");}); }).then(value => {console.log(value); }).then(value => {console.log(value); })5)異常穿透
(1) 當使用 Promise 的 then 鏈式調用時, 可以在最后指定失敗的回調
(2) 前面任何操作出了異常, 都會傳到最后失敗的回調中處理
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');// reject('Err');}, 1000); }); ? p.then(value => {// console.log(111);throw '失敗啦!'; }).then(value => {console.log(222); }).then(value => {console.log(333); }).catch(reason => {console.warn(reason); });6)中斷Promise鏈條
(1) 當使用 promise 的 then 鏈式調用時, 在中間中斷, 不再調用后面的回調函數
(2) 辦法: 在回調函數中返回一個 pendding 狀態的 promise 對象
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve('OK');}, 1000); }); ? p.then(value => {console.log(111);//有且只有一個方式return new Promise(() => {}); }).then(value => {console.log(222); }).then(value => {console.log(333); }).catch(reason => {console.warn(reason); });4. Async-Await
4.1 Async
-
函數的返回值為 Promise 對象
-
Promise 對象的結果由 async 函數執行的返回值決定
-
async必須聲明的是一個function
sync聲明的函數的返回本質上是一個Promise。所以你想像正常function的返回那樣,拿到返回值,你可以這樣拿到返回值:
main().then(result=>{console.log(result); })4.2 Await
1)await 右側的表達式一般為 promise 對象, 但也可以是其它的值
2)如果表達式是 promise 對象, await 返回的是 promise 成功的值(await會等待promise返回結果后,再繼續執行)
3)如果表達式是其它值, 直接將此值作為 await 的返回值(會立即執行,不過就沒有意義了)
async function main(){let p = new Promise((resolve, reject) => {// resolve('OK');reject('Error');})//1. 右側為promise的情況// let res = await p;//2. 右側為其他類型的數據// let res2 = await 20;// 可以用標準的try catch語法捕捉錯誤try{let res3 = await p;console.log(res3);}catch(e){console.log(e);} } ? main();注意:
-
await 必須寫在 async 函數中, 但 async 函數中可以沒有 await
-
await等待的雖然是Promise對象,但不必寫 .then(...),直接可以得到返回值。
-
如果 await 的 promise 失敗了, 就會拋出異常, 需要通過 try...catch 捕獲處理
4.3 async/await的中斷
首先我們要明確的是,Promise本身是無法中止的,Promise本身只是一個狀態機,存儲三個狀態(pending,resolved,rejected),一旦發出請求了,必須閉環,無法取消,之前處于pending狀態只是一個掛起請求的狀態,并不是取消,一般不會讓這種情況發生,只是用來臨時中止鏈式的進行。
中斷(終止)的本質在鏈式中只是掛起,并不是本質的取消Promise請求,那樣是做不到的,Promise也沒有cancel的狀態。
不同于Promise的鏈式寫法,寫在async/await中想要中斷程序就很簡單了,因為語義化非常明顯,其實就和一般的function寫法一樣,想要中斷的時候,直接return一個值就行,null,空,false都是可以的。看例子:
? ?const setDelay = (millisecond) => {return new Promise((resolve, reject)=>{if (typeof millisecond !== 'number') reject(new Error('參數必須是number類型'));setTimeout(()=> {resolve(`我延遲了${millisecond}毫秒后輸出的`)}, millisecond)})} ?let count = 6;const demo = async ()=>{const result = await setDelay(3000);console.log(result);if (count > 5) {return '我退出了,下面的不進行了';// return;// return false; // 這些寫法都可以// return null;// return Promise.reject(new Error('拒絕'));}console.log(await setDelay(1000));console.log('完成了');};demo().then(result=>{console.log(result);}).catch(err=>{console.log(err);})實質就是直接return返回了一個Promise,相當于return Promise.resolve('我退出了下面不進行了'),當然你也可以返回一個“拒絕”:return Promise.reject(new Error('拒絕'))那么就會進到錯誤信息里去。
async函數實質就是返回一個Promise!
4.4 async和await的結合
下面我們用兩個案例演示一下:
1)讀取多個文件,并輸出
const fs = require('fs'); const util = require('util'); const mineReadFile = util.promisify(fs.readFile); ? ? //async 與 await async function main(){try{//讀取第一個文件的內容let data1 = await mineReadFile('./resource/1.html');let data2 = await mineReadFile('./resource/2.html');let data3 = await mineReadFile('./resource/3.html');console.log(data1 + data2 + data3);}catch(e){console.log(e);} } ? main();2)發送ajax請求
? ?<button id="btn">點擊獲取段子</button><script>function sendAJAX(url){return new Promise((resolve, reject) => {const xhr = new XMLHttpRequest();xhr.responseType = 'json';xhr.open("GET", url);xhr.send();//處理結果xhr.onreadystatechange = function(){if(xhr.readyState === 4){//判斷成功if(xhr.status >= 200 && xhr.status < 300){//成功的結果resolve(xhr.response);}else{reject(xhr.status);}}}});} ?//段子接口地址 https://api.apiopen.top/getJokelet btn = document.querySelector('#btn'); ?btn.addEventListener('click',async function(){//獲取段子信息let duanzi = await sendAJAX('https://api.apiopen.top/getJoke');console.log(duanzi);});</script>?
總結
以上是生活随笔為你收集整理的Promise和Async-Await的入门教程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webpack之optimization
- 下一篇: webpack从入门到精通(四)优化打包