一、認識Promise
ES6中一個非常重要和好用的特性就是Promise
- 但是初次接觸Promise會一臉懵逼,這TM是什麼東西?
- 看看官方或者一些文章對它的介紹和用法,也是一頭霧水。
Promise到底是做什麼的呢?
- Promise是異步程式設計的一種解決方案。
那什麼時候我們會來處理異步事件呢?
- 一種很常見的場景應該就是網絡請求了。
- 我們封裝一個網絡請求的函數,因為不能立即拿到結果,是以不能像簡單的3+4=7一樣将結果傳回。
- 是以往往我們會傳入另外一個函數,在資料請求成功時,将資料通過傳入的函數回調出去。
- 如果隻是一個簡單的網絡請求,那麼這種方案不會給我們帶來很大的麻煩。
但是,當網絡請求非常複雜時,就會出現回調地獄。
OK,我以一個非常誇張的案例來說明。
我們來考慮下面的場景(有誇張的成分):
- 我們需要通過一個url1從伺服器加載一個資料data1,data1中包含了下一個請求的url2
- 我們需要通過data1取出url2,從伺服器加載資料data2,data2中包含了下一個請求的url3
- 我們需要通過data2取出url3,從伺服器加載資料data3,data3中包含了下一個請求的url4
- 發送網絡請求url4,擷取最終的資料data4
上面的代碼有什麼問題嗎?
- 正常情況下,不會有什麼問題,可以正常運作并且擷取我們想要的結果。
- 但是,這樣額代碼難看而且不容易維護。
我們更加期望的是一種更加優雅的方式來進行這種異步操作。
如何做呢?
- 就是使用Promise。
- Promise可以以一種非常優雅的方式來解決這個問題。
二、Promise的基本使用
2.1 定時器的異步事件
我們先來看看Promise最基本的文法。
這裡,我們用一個定時器來模拟異步事件:
- 假設下面的data是從網絡上1秒後請求的資料
- console.log就是我們的處理方式。
上圖是我們過去的處理方式,我們将它換成Promise代碼:
這個例子會讓我們感覺脫褲放屁,多此一舉
- 首先,下面的Promise代碼明顯比上面的代碼看起來還要複雜。
- 其次,下面的Promise代碼中包含的resolve、reject、then、catch都是些什麼東西?
我們先不管第一個複雜度的問題,因為這樣的一個屁大點的程式根本看不出來Promise真正的作用。
2.2 定時器異步事件解析
我們先來認認真真的讀一讀這個程式到底做了什麼?
- new Promise很明顯是建立一個Promise對象
- 小括号中((resolve, reject) => {})也很明顯就是一個函數,而且我們這裡用的是之前剛剛學習過的箭頭函數。
但是resolve, reject它們是什麼呢?
- 我們先知道一個事實:在建立Promise時,傳入的這個箭頭函數是固定的(一般我們都會這樣寫)
- resolve和reject它們兩個也是函數,通常情況下,我們會根據請求資料的成功和失敗來決定調用哪一個。
成功還是失敗?
- 如果是成功的,那麼通常我們會調用resolve(messsage),這個時候,我們後續的then會被回調。
- 如果是失敗的,那麼通常我們會調用reject(error),這個時候,我們後續的catch會被回調。
OK,這就是Promise最基本的使用了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 1.使用setTimeout
// setTimeout(() => {
// console.log('Hello world!');
// }, 1000)
// new Promise(參數) 參數->函數(resolve,reject)
// resolve,reject它們本身又是函數
// 鍊式程式設計
/*new Promise((resolve,reject) => {
// 第一次發生網絡請求的代碼
setTimeout(() => {
// console.log('Hello world!');
resolve()
}, 1000)
}).then(() => {
// 第一次拿到結果後的處理代碼
console.log('Hello world!');
console.log('Hello world!');
return new Promise((resolve, reject) => {
// 第二次發生網絡請求的代碼
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
// 第二次拿到結果後的處理代碼
console.log('Hello vuejs!');
console.log('Hello vuejs!');
return new Promise((resolve, reject) => {
// 第三次發生網絡請求的代碼
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
// 第三次拿到結果後的處理代碼
console.log('Hello promise!');
console.log('Hello promise!');
})
})
})*/
// 什麼情況下會用到Promise?
// 一般情況下是有異步操作時,會使用Promise對這個異步操作進行封裝
new Promise((resolve, reject) => {
setTimeout(() => {
// 成功的時候調用resolve
// resolve('hello data')
// 失敗的時候調用reject
reject('error message')
}, 1000)
}).then((data) => {
console.log(data);
console.log(data);
console.log(data);
console.log(data);
}).catch((err) => {
console.log(err);
})
</script>
</body>
</html>
- then()處理成功,catch()處理失敗:
- 不寫catch(),把處理成功和失敗的兩個函數都作為then()函數的參數傳進去:
三、Promise三種狀态
首先, 當我們開發中有異步操作時, 就可以給異步操作包裝一個Promise
異步操作之後會有三種狀态,我們一起來看一下這三種狀态:
- pending:等待狀态,比如正在進行網絡請求,或者定時器沒有到時間。
- fulfill:滿足狀态,當我們主動回調了resolve時,就處于該狀态,并且會回調.then()
- reject:拒絕狀态,當我們主動回調了reject時,就處于該狀态,并且會回調.catch()
四、Promise的鍊式程式設計
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 1.使用setTimeout
// setTimeout(() => {
// console.log('Hello world!');
// }, 1000)
// new Promise(參數) 參數->函數(resolve,reject)
// resolve,reject它們本身又是函數
// 鍊式程式設計
new Promise((resolve,reject) => {
// 第一次發生網絡請求的代碼
setTimeout(() => {
// console.log('Hello world!');
resolve()
}, 1000)
}).then(() => {
// 第一次拿到結果後的處理代碼
console.log('Hello world!');
console.log('Hello world!');
return new Promise((resolve, reject) => {
// 第二次發生網絡請求的代碼
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
// 第二次拿到結果後的處理代碼
console.log('Hello vuejs!');
console.log('Hello vuejs!');
return new Promise((resolve, reject) => {
// 第三次發生網絡請求的代碼
setTimeout(() => {
resolve()
}, 1000)
}).then(() => {
// 第三次拿到結果後的處理代碼
console.log('Hello promise!');
console.log('Hello promise!');
})
})
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 網絡請求: aaa -> 處理代碼(10行代碼)
// 處理:aaa111 -> 處理代碼(10行代碼)
// 處理:aaa111222 -> 處理代碼(10行代碼)
// new Promise((resolve, reject) => {})
/* new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(res => {
// 1.自己處理10行代碼
console.log(res, '第一層的10行處理代碼')
// 2.對結果進行第一次處理
return new Promise((resolve, reject) => {
resolve(res + '111')
})
}).then(res => {
console.log(res, '第二層的10處理代碼');
return new Promise((resolve, reject) => {
resolve(res + '222')
})
}).then(res => {
console.log(res, '第三層的10處理代碼');
})
*/
// 2.
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('aaa')
// }, 1000)
// }).then(res => {
// // 1.自己處理10行代碼
// console.log(res,'第一層的10行處理代碼')
//
// // 2.對結果進行第一次處理
// // return new Promise((resolve, reject) => {
// // resolve(res + '111')
// // })
// return Promise.resolve(res + '111')
// }).then(res => {
// console.log(res, '第二層的10處理代碼');
//
// return Promise.resolve(res + '222')
//
// }).then(res => {
// console.log(res, '第三層的10處理代碼');
// })
// 鍊式調用中間某一層出現reject的情況,會直接執行最後面的catch()
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('aaa')
// }, 1000)
// }).then(res => {
// // 1.自己處理10行代碼
// console.log(res, '第一層的10行處理代碼')
//
// // 2.對結果進行第一次處理
// // return new Promise((resolve, reject) => {
// // resolve(res + '111')
// // })
// return Promise.reject('error message')
// }).then(res => {
// console.log(res, '第二層的10處理代碼');
//
// return Promise.resolve(res + '222')
//
// }).then(res => {
// console.log(res, '第三層的10處理代碼');
// }).catch(err => {
// console.log(err);
// })
// throw
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('aaa')
}, 1000)
}).then(res => {
// 1.自己處理10行代碼
console.log(res, '第一層的10行處理代碼')
// 2.對結果進行第一次處理
// return new Promise((resolve, reject) => {
// resolve(res + '111')
// })
throw 'error message'
}).then(res => {
console.log(res, '第二層的10處理代碼');
return Promise.resolve(res + '222')
}).then(res => {
console.log(res, '第三層的10處理代碼');
}).catch(err => {
console.log(err);
})
// 3.省略掉Promise.resolve
// new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('aaa')
// }, 1000)
// }).then(res => {
// // 1.自己處理10行代碼
// console.log(res, '第一層的10行處理代碼')
//
// return res + '111'
// }).then(res => {
// console.log(res, '第二層的10處理代碼');
//
// return res + '222'
//
// }).then(res => {
// console.log(res, '第三層的10處理代碼');
// })
</script>
</body>
</html>
4.1 鍊式調用
- 我們在看Promise的流程圖時,發現無論是then還是catch都可以傳回一個Promise對象。
- 是以,我們的代碼其實是可以進行鍊式調用的:
- 這裡我們直接通過Promise包裝了一下新的資料,将Promise對象傳回了
- Promise.resovle():将資料包裝成Promise對象,并且在内部回調resolve()函數
- Promise.reject():将資料包裝成Promise對象,并且在内部回調reject()函數
4.2 鍊式調用簡寫
簡化版代碼:
如果我們希望資料直接包裝成Promise.resolve,那麼在then中可以直接傳回資料
- 注意下面的代碼中,我将
改成了return Promise.resovle(data)
,結果依然是一樣的return data
4.3 all方法的使用
當需要多個網絡請求都成功後才能進行下一步操作時,可以使用Promise的all方法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// Promise.all([
// new Promise((resolve, reject) => {
// $ajax({
// url: 'url1',
// success: function (data) {
// resolve(data)
// }
// })
// }),
//
// new Promise((resolve, reject) => {
// $ajax({
// url: 'url2',
// success: function (data) {
// resolve(data)
// }
// })
// })
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result1')
},2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result2')
},1000)
})
]).then(results => {
console.log(results);
})
</script>
</body>
</html>