Promise
是個(gè)啥?
馬上拿起來(lái)了一個(gè)有道詞典
查了一下.
有承諾,保證
的意思.
很多電影里都有這句臺(tái)詞:I Promise You.
是什么問(wèn)題導(dǎo)致了ES6要給一個(gè)Promise?
在開(kāi)發(fā)web應(yīng)用的過(guò)程中,AJAX
異步請(qǐng)求數(shù)據(jù)很常見(jiàn).
$.get('./data/users/1',function(data){
//.....
})
一個(gè)頁(yè)面請(qǐng)求多個(gè)接口,每個(gè)接口之間有相互依賴關(guān)系也很常見(jiàn).
$.get('./data/users/1',function(data){
$.get('./data/products/${data.userid}',function(data){
$.get('./data/accountInfo/${data.productAccountInfo}',function(data){
//......
})
})
})
有問(wèn)題嗎? callback
被丟到了 Event Loop
里了,執(zhí)行時(shí)機(jī)除了它自己,沒(méi)人能知道.所以,為了獲取上一個(gè)接口的數(shù),我們需要在它里面在發(fā)送下一個(gè) Ajax
請(qǐng)求.
強(qiáng)行問(wèn)題在于:如果這樣的接口依賴很多呢?
所謂的callback hell --- 回調(diào)地獄??
對(duì),代碼從原來(lái)的線性自上而下,變成了現(xiàn)在的橫向發(fā)展.
但不管它們變成什么樣.
起碼在上述和我簡(jiǎn)單的demo中的代碼,有幾個(gè)共同點(diǎn)
- 都是 callback 回調(diào)的方法.
- 都是下一個(gè)接口請(qǐng)求依賴上一個(gè)接口返回的數(shù)據(jù).
Promise 如何解決代上述代碼橫向發(fā)展的問(wèn)題的?
為了解決代碼丑陋和難以維護(hù)(代碼寫(xiě)的太臃腫,太丑了,確實(shí)也就變得很難維護(hù)了)問(wèn)題.
于是 ES6 新推出了一個(gè)叫 Promise 的玩意.
感性認(rèn)知一下
userDataPromise('./data/users/1')
.then((data)=>{
// 在這里拿到了用戶數(shù)據(jù)
return productsDataPrmoise('./data/products/${data.userid}')
})
.then((data)=>{
// 在這里拿到的商品信息
return accountDataPromise('./data/accountInfo/${data.productAccountInfo}')
})
.then((data)=>{
// 在這里拿到了賬戶信息,然后該做什么就作什么.
//.....
})
這個(gè)和上述使用 $.get
的共同點(diǎn).
- 都是一個(gè)請(qǐng)求發(fā)完之后,接著發(fā)下一個(gè)請(qǐng)求.下一個(gè)請(qǐng)求依賴上一個(gè)請(qǐng)求的數(shù)據(jù).
- 每個(gè)
Promise
使用then???
來(lái)執(zhí)行回調(diào)函數(shù). - 代碼的結(jié)構(gòu)是很舒服的縱向發(fā)展.
這個(gè)和上述 $.get
的不同點(diǎn)
-
$.get
的回調(diào)函數(shù),我們是在一個(gè)方法(,function(){callbackhere})
里寫(xiě)的. - 第二個(gè)
$.get
嵌套在了第一個(gè)$.get的callback
里面. - 代碼是橫向發(fā)展的
僅僅是代碼從嵌套橫向變成了then縱向了而已啊?那我把回調(diào)函數(shù)放在外面賦值,不給里面不就行了?
function get(url) {
var oReq = new XMLHttpRequest()
// oReq.onload = function () {
// callback(JSON.parse(oReq.responseText))
// }
oReq.open('GET', url, true)
oReq.send()
return oReq // 返回這個(gè)是為了拿到 onload & oReq.responseText
}
var xhr = get('./data/1.json')
// 把異步callback注冊(cè)到Event Loop 的操作仍然是同步的,t同步的再慢,也比異步的要快.我就不相信會(huì)出現(xiàn),我xhr2 還沒(méi)執(zhí)行完,xhr 的 onload 回調(diào)函數(shù)就執(zhí)行了的情況!!!
// 同步代碼的執(zhí)行優(yōu)先權(quán)永遠(yuǎn)大于異步執(zhí)行代碼.
xhr.onload = ()=>{
console.log(JSON.parse(xhr.responseText))
}
var xhr2 = get('./data/2.json')
xhr2.onload = () => {
console.log(JSON.parse(xhr2.responseText))
}
var xhr3 = get('./data/3.json')
xhr3.onload = () => {
console.log(JSON.parse(xhr3.responseText))
}
// 對(duì)比
$.get('./data/users/1',function(data){
$.get('./data/products/${data.userid}',function(data){
$.get('./data/accountInfo/${data.productAccountInfo}',function(data){
//......
})
})
})
// 無(wú)非就是把異步函數(shù)注冊(cè)代碼的步驟平移了出來(lái),僅此而已.
Promise
的出現(xiàn)原因之一,就是為了讓我們不要在寫(xiě)那種 callback hell
那樣的代碼結(jié)構(gòu)了嗎?
Promise 的基本使用.
Promise
是解決啥的?
解決多個(gè)異步回調(diào)函數(shù)之間嵌套寫(xiě)法的問(wèn)題
意思就說(shuō),如果沒(méi)有異步,都是同步的代碼,就用不上Promise了
所以,用Promise主要是用在異步上.
可以Promise想象成一個(gè)異步操作的容器.
Promise承諾你,當(dāng)這個(gè)異步完成之后,不管成功還是失敗,只要你指定了對(duì)應(yīng)的回調(diào)函數(shù),我都會(huì)去執(zhí)行.
是不是感覺(jué)很廢?
- 異步操作,我要裝在你里面去
- 什么是成功,什么是失敗我也要告訴你.
但為了解決多個(gè)異步嵌套導(dǎo)致代碼橫向擴(kuò)展的問(wèn)題,咱們還是去用吧.畢竟逼格高一點(diǎn).
- 首先我們需要實(shí)例化一個(gè)
Promise對(duì)象
(對(duì)Promise是一個(gè)構(gòu)造函數(shù))
new Promise()
- 此構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù).
new Prmoise(function....)
- 此函數(shù)參數(shù)包含兩個(gè)形參(reslove,reject)
new Promise(function(reslove,reject){})
- 最后是完全體
new Promise(function(reslove,reject){
$.get('./data/users/1',function(data){
if (data.success) reslove(data)
reject(data.error)
})
})
- 要把異步操作給
Promise
==>$.get('./data/users/1'
- 告訴
Promise
啥是成功 ==>reslove(data)
- 啥是失敗 ==>
reject(data.error)
接著就是使用我們剛new出來(lái)的Promise
.
既然是new出來(lái)的,我們就拿個(gè)對(duì)象去接受.
var p = new Promise(function(reslove,reject){
$.get('./data/users/1',function(data){
if (data.success) reslove(data)
reject(data.error)
})
})
我們?cè)诮oPromise傳參的時(shí)候,指定了 reslove & reject 兩個(gè)函數(shù)的形參,如何傳遞這兩個(gè)的實(shí)參呢?
p.then((data)=>{},(err)=>{})
使用實(shí)例對(duì)象的 then
方法,接受兩個(gè)參數(shù),順序是 then(resloveCallback,rejectCallback)
最后完整的使用,并發(fā)送請(qǐng)求.
var p = new Promise(function(reslove,reject){
$.get('./data/users/1',function(data){
if (data.success) reslove(data)
reject(data.error)
})
})
p.then((data)=>{
console.log(data) // 數(shù)據(jù)請(qǐng)求成功
},(err)=>{
console.log(err) // 數(shù)據(jù)請(qǐng)求失敗.
})
到這一步是不是很無(wú)聊?
無(wú)非就是提供了一個(gè)then
方法,讓我們來(lái)指定成功和失敗的回調(diào)函數(shù)實(shí)參.....
Promise 的一些其他特性.
官方說(shuō)明:
Promise 是異步編程的一種解決方案匠襟,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大艰争。它由社區(qū)最早提出和實(shí)現(xiàn)互纯,ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn)隐锭,統(tǒng)一了用法刚照,原生提供了Promise對(duì)象衷掷。
Promise對(duì)象有以下兩個(gè)特點(diǎn)印蔬。
- 對(duì)象的狀態(tài)不受外界影響回论。Promise對(duì)象代表一個(gè)異步操作浩姥,有三種狀態(tài):pending(進(jìn)行中)挑随、fulfilled(已成功)和rejected(已失敗)勒叠。只有異步操作的結(jié)果兜挨,可以決定當(dāng)前是哪一種狀態(tài)膏孟,任何其他操作都無(wú)法改變這個(gè)狀態(tài)。這也是Promise這個(gè)名字的由來(lái)拌汇,它的英語(yǔ)意思就是“承諾”柒桑,表示其他手段無(wú)法改變。
- 一旦狀態(tài)改變噪舀,就不會(huì)再變幕垦,任何時(shí)候都可以得到這個(gè)結(jié)果。Promise對(duì)象的狀態(tài)改變傅联,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected先改。只要這兩種情況發(fā)生,狀態(tài)就凝固了蒸走,不會(huì)再變了仇奶,會(huì)一直保持這個(gè)結(jié)果,這時(shí)就稱為 resolved(已定型)比驻。如果改變已經(jīng)發(fā)生了该溯,你再對(duì)Promise對(duì)象添加回調(diào)函數(shù),也會(huì)立即得到這個(gè)結(jié)果别惦。這與事件(Event)完全不同狈茉,事件的特點(diǎn)是,如果你錯(cuò)過(guò)了它掸掸,再去監(jiān)聽(tīng)氯庆,是得不到結(jié)果的。
根據(jù)文檔畫(huà)一張很無(wú)聊的圖.
Promise 的基本套路
還是直接上代碼來(lái)的實(shí)際.
由于 Promise 的 then 方法,又返回了一個(gè) Promise 對(duì)象的類型,所以,Promise對(duì)象可以不停的then.
這就為 Promise 對(duì)象鏈?zhǔn)秸{(diào)用提供了基礎(chǔ).
function createReslovePromise() {
return new Promise(function (reslove,reject) {
setTimeout(function () {
reslove(data)
},1000)
})
}
createReslovePromise()
.then(() => {
return 1
})
.then((data) => {
console.log(data)
return 2
})
.then((data) => {
console.log(data)
})
relax-2:測(cè)試 relax$ node Promise.js
1
2
relax-2:測(cè)試 relax$
圖解:
上一個(gè) then
中reslove
的返回值,就是下一個(gè)then
中reslove
的參數(shù)
但是如果,,我在上一個(gè)Promise對(duì)象
的then
的reslove
中,成功接受到了成功數(shù)據(jù)之后,就返回下一個(gè) Promise對(duì)象
.
那么下一個(gè)then就是上一個(gè)返回的Promise對(duì)象的then了.
function createReslovePromise(stepName) {
return new Promise(function (reslove,reject) {
setTimeout(function () {
reslove(stepName)
},1000)
})
}
createReslovePromise('step 01')
.then((data) => {
console.log(data) // 數(shù)據(jù)收到了,返回下一個(gè)Promise
return createReslovePromise('step 02')
})
.then((data) => {
console.log(data)
return createReslovePromise('step 03')
})
.then((data) => {
console.log(data)
})
relax-2:測(cè)試 relax$ node Promise.js
step 01
step 02
step 03
圖解
基于上面這個(gè)then
返回 Promise
的特性,就可以完成鏈?zhǔn)降闹隙碌漠惒酱a結(jié)構(gòu)了.
使用Promise請(qǐng)求一個(gè)瞎編的邏輯
- 首先請(qǐng)求
./data/users/1
拿到id為1的用戶 - 接著請(qǐng)求
./data/preferences/{user.preferences}
在根據(jù)拿到用戶的preferences
拿到用戶的偏好設(shè)置里面的bobbies
- 最后根據(jù)用戶的
preferences.hobbies
拿到用戶的愛(ài)好.../data/hobbies/{preferences.hobbies}
{
"users":[
{"id":1,"name":"張三","preferences":1},
{"id":2,"name":"李四","preferences":2},
{"id":3,"name":"王五","preferences":3},
{"id":4,"name":"趙六","preferences":4}
],
"preferences":[
{"id":1,"hobbies":1},
{"id":2,"hobbies":2},
{"id":3,"hobbies":3},
{"id":4,"hobbies":4}
],
"hobbies":[
{"id":1,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]},
{"id":2,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]},
{"id":3,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]},
{"id":4,"values":["看書(shū),打游戲,聽(tīng)歌,騎行"]}
]
}
啟動(dòng)一個(gè) json-server
服務(wù)
json-server --watch db.json
服務(wù)啟動(dòng)成功
測(cè)試一下
使用傳統(tǒng)的 $.get 方式
$.get('http://localhost:8000/users/1',function(data){
let userInfo = data
console.log(data)
$.get(`http://localhost:8000/preferences/${userInfo.preferences}`,function(data){
console.log(data)
let userPreferences = data
$.get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`,function(data){
let userHobbies = data
console.log(data)
var hobbies = userHobbies.values && userHobbies.values.join(',')
document.querySelector('h1').innerText = hobbies
})
})
})
好像有callback hell
回調(diào)地獄了.
使用原生XMLHttpRequest
并回調(diào)函數(shù)平移出來(lái)的方式
var xhr = get('http://localhost:8000/users/1')
xhr.onload = function () {
let jsonData = JSON.parse(xhr.responseText)
let userInfo = jsonData
console.log(jsonData)
var xhr2 = get(`http://localhost:8000/preferences/${userInfo.preferences}`)
xhr2.onload = () => {
let jsonData = JSON.parse(xhr2.responseText)
let userPreferences = jsonData
console.log(userPreferences)
var xhr3 = get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`)
xhr3.onload = () => {
let jsonData = JSON.parse(xhr3.responseText)
let userHobbies = jsonData
var hobbies = userHobbies.values && userHobbies.values.join(',')
document.querySelector('h1').innerText = hobbies
}
}
}
function get(url) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.send()
return xhr
}
突然發(fā)現(xiàn)問(wèn)題出來(lái)了,如果有依賴性的接口請(qǐng)求關(guān)系,不管怎么把回調(diào)函數(shù)移出來(lái),最后還是會(huì)被迫的寫(xiě)成 callback hell
的形式.
最后在使用 Promise
function pGet(url) {
return new Promise(function (reslove, reject) {
get(url, function (data) {
reslove(data)
})
})
}
function get(url, callback) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.send()
xhr.onload = function () {
callback && typeof callback === 'function' && callback(JSON.parse(xhr.responseText))
}
}
pGet('http://localhost:8000/users/1')
.then(function(data){
console.log(data)
let userInfo = data
return pGet(`http://localhost:8000/preferences/${userInfo.preferences}`)
})
.then((data)=>{
console.log(data)
let preferences = data
return pGet(`http://localhost:8000/hobbies/${preferences.hobbies}`)
})
.then((data)=>{
console.log(data)
let hobbies = data
var hobbiesStr = hobbies && hobbies.values instanceof Array && hobbies.values.join(',')
document.querySelector('h1').innerText = hobbiesStr
})
發(fā)現(xiàn)代碼最終是按照豎向的走向往下寫(xiě)的.
確實(shí)是避免了代碼橫向走的問(wèn)題.
使用Promise確實(shí)可以比較優(yōu)雅的寫(xiě)出有依賴關(guān)系接口方法的代碼層級(jí)結(jié)構(gòu).所以,它確實(shí)解決了callback hell
寫(xiě)法的問(wèn)題.