目錄
一. Promise
?1. 為什么要使用Promise
?2. Promise是什么
?3. 如何使用Promise
?4.
fetch
纹份、AsyncStorage
使用示例二.
async-await
一. Promise
1. 為什么要使用Promise
關(guān)于事件循環(huán)吕漂、線程、隊(duì)列套耕、同步任務(wù)谁帕、異步任務(wù),這里就不展開(kāi)了冯袍,簡(jiǎn)單說(shuō)下它們?cè)贘S里的情況匈挖。
為了程序執(zhí)行的簡(jiǎn)單,JS被設(shè)計(jì)為單線程的康愤,也就是說(shuō)JS里的所有任務(wù)都是在主線程里執(zhí)行的儡循,下一個(gè)任務(wù)必須得等上一個(gè)任務(wù)執(zhí)行完畢才能執(zhí)行,這也就是同步任務(wù)征冷。但是如果一個(gè)任務(wù)的執(zhí)行時(shí)間可能很長(zhǎng)(如一個(gè)任務(wù)里包含了網(wǎng)絡(luò)請(qǐng)求择膝、數(shù)據(jù)庫(kù)讀寫(xiě)等IO操作),它就會(huì)阻塞主線程检激,導(dǎo)致后面的任務(wù)也無(wú)法執(zhí)行肴捉,不過(guò)讓后面的任務(wù)等也不是不行,如果是因?yàn)槟硞€(gè)任務(wù)計(jì)算量大而導(dǎo)致CPU忙不過(guò)來(lái)叔收,那這個(gè)等就是不可避免的每庆,也是有效的,但很多情況的等卻是任務(wù)中IO操作的部分出結(jié)果很慢今穿,導(dǎo)致我們一直拿不到結(jié)果,CPU就只能在那閑著干等伦籍,等到結(jié)果后再繼續(xù)執(zhí)行該任務(wù)蓝晒。于是JS的設(shè)計(jì)者就設(shè)計(jì)腮出,CPU完全可以不管某個(gè)任務(wù)中IO操作的部分,當(dāng)某個(gè)任務(wù)執(zhí)行IO操作的時(shí)候芝薇,就掛起這個(gè)任務(wù)胚嘲,并把這個(gè)任務(wù)放到一個(gè)專(zhuān)門(mén)的隊(duì)列中去,讓CPU繼續(xù)執(zhí)行后面的任務(wù)洛二,等IO操作返回了結(jié)果馋劈,再把這個(gè)任務(wù)從隊(duì)列里拿出來(lái)放到主線程中繼續(xù)執(zhí)行下去,于是這種任務(wù)就成了異步任務(wù)晾嘶,它不會(huì)阻塞主線程妓雾,而這種一遍一遍不停地檢查異步任務(wù)是否該繼續(xù)執(zhí)行的機(jī)制就是JS里面的事件循環(huán)機(jī)制(Event Loop)。
一個(gè)異步任務(wù)的通常寫(xiě)法都是:IO操作 + 回調(diào)函數(shù)垒迂。IO操作為一種耗時(shí)操作械姻,回調(diào)函數(shù)用來(lái)指定耗時(shí)操作結(jié)束后接下來(lái)該任務(wù)要干什么。JS里設(shè)計(jì)如果一個(gè)異步任務(wù)沒(méi)有回調(diào)函數(shù)机断,那么它在執(zhí)行IO操作被掛起后楷拳,就不會(huì)把它放入任務(wù)隊(duì)列中,那么當(dāng)IO操作返回結(jié)果后吏奸,它也就不會(huì)再次進(jìn)入主線程繼續(xù)執(zhí)行了欢揖,因?yàn)樗鼪](méi)有用回調(diào)函數(shù)指定下一步要干什么。但如果異步任務(wù)指定了回調(diào)函數(shù)奋蔚,那么當(dāng)異步任務(wù)重新進(jìn)入主線程時(shí)她混,就是執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。
下面我們舉例子來(lái)看看異步任務(wù)的編寫(xiě)旺拉。
假定f1
要做個(gè)異步任務(wù)产上,f2
是f1
的回調(diào)函數(shù)。
function f1(callback) {
// f1任務(wù)的耗時(shí)代碼
// f1任務(wù)的耗時(shí)代碼執(zhí)行完后蛾狗,執(zhí)行回調(diào)函數(shù)
callback();
}
f1(f2);
使用回調(diào)函數(shù)法來(lái)實(shí)現(xiàn)異步任務(wù)的優(yōu)點(diǎn)是簡(jiǎn)單和容易理解晋涣,但是卻可能出現(xiàn)下面這樣的使用情況。
function async(arg, callback) {
console.log('參數(shù)為 ' + arg +' , 1秒后返回結(jié)果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function final(value) {
console.log('完成: ', value);
}
async(1, function (value) {
async(2, function (value) {
async(3, function (value) {
async(4, function (value) {
async(5, function (value) {
async(6, final);
});
});
});
});
});
// 參數(shù)為 1 , 1秒后返回結(jié)果
// 參數(shù)為 2 , 1秒后返回結(jié)果
// 參數(shù)為 3 , 1秒后返回結(jié)果
// 參數(shù)為 4 , 1秒后返回結(jié)果
// 參數(shù)為 5 , 1秒后返回結(jié)果
// 參數(shù)為 6 , 1秒后返回結(jié)果
// 完成: 12
如果像上面這樣沉桌,異步任務(wù)的回調(diào)函數(shù)又是一個(gè)異步任務(wù)谢鹊,那回調(diào)函數(shù)就會(huì)一直嵌套下去,此時(shí)代碼的結(jié)構(gòu)就有點(diǎn)亂了留凭,我們也無(wú)法從代碼中清晰地閱讀出每個(gè)異步任務(wù)的耗時(shí)任務(wù)完成后佃扼,接下來(lái)要做什么。
Promise的出現(xiàn)蔼夜,就是為了解決異步任務(wù)的回調(diào)函數(shù)可能過(guò)于臃腫和不易閱讀的問(wèn)題兼耀,下面我們來(lái)詳細(xì)看看它。
2. Promise是什么
Promise的主要用途就是通過(guò)then
、catch
等方法來(lái)給異步任務(wù)設(shè)置回調(diào)瘤运,代替掉原來(lái)回調(diào)函數(shù)的那種實(shí)現(xiàn)方案窍霞,從而使得整個(gè)異步任務(wù)的流程更清晰,代碼更易讀拯坟。例如上面的例子但金,使用Promise后如下。
(new Promise(stpe1))
.then(step2)
.then(step3)
.then(step4);
Promise是一個(gè)對(duì)象郁季,也是一個(gè)構(gòu)造函數(shù)冷溃。Promise構(gòu)造函數(shù)接受一個(gè)函數(shù)f1
作為參數(shù),f1
里面是異步任務(wù)的代碼梦裂,返回的p1
就是一個(gè)Promise對(duì)象似枕。下面一小節(jié)我們會(huì)做詳細(xì)的介紹。
var p1 = new Promise(f1);
Promise對(duì)象有三種狀態(tài)塞琼。
- 異步操作進(jìn)行中(
pending
) - 異步操作成功(
fulfilled
) - 異步操作失敳ぞ弧(
rejected
)
這三種狀態(tài)之間的轉(zhuǎn)變只有兩種可能,而且一旦狀態(tài)發(fā)生變化彪杉,就凝固了毅往,不會(huì)再發(fā)生變化。
- 異步操作進(jìn)行中 ——> 異步操作成功
- 異步操作進(jìn)行中 ——> 異步操作失敗
因此派近,Promise對(duì)象的最終狀態(tài)只有兩種攀唯。
- 異步操作成功
- 異步操作失敗
注意:
fulfilled
和rejected
兩種狀態(tài)合在一起又可以稱(chēng)為resolved
狀態(tài)(已定型),但是為了行文方便渴丸,本篇后面的resolved
狀態(tài)統(tǒng)一只指fulfilled
狀態(tài)侯嘀,不包含rejected
狀態(tài)。
3. 如何使用Promise
- 第一步:使用Promise構(gòu)造函數(shù)谱轨,通過(guò)固定的格式來(lái)包裹異步任務(wù)戒幔,并將異步任務(wù)的執(zhí)行結(jié)果或錯(cuò)誤傳遞出去
有了Promise之后,如果我們想給某個(gè)異步任務(wù)添加回調(diào)函數(shù)土童,就不是編寫(xiě)一個(gè)普通的函數(shù)诗茎,在內(nèi)部做異步任務(wù)并執(zhí)行回調(diào)函數(shù)了,而是直接使用Promise構(gòu)造函數(shù)献汗,通過(guò)固定的格式來(lái)包裹異步任務(wù)敢订,并將異步任務(wù)的執(zhí)行結(jié)果或錯(cuò)誤傳遞出去。
const promise = new Promise(function (resolve, reject) {
// some code...
if (/* 異步任務(wù)執(zhí)行成功 */){
resolve(value);
} else { /* 異步任務(wù)執(zhí)行失敗 */
reject(error);
}
});
上面代碼中罢吃,Promise構(gòu)造函數(shù)接收一個(gè)函數(shù)作為參數(shù)楚午。該參數(shù)函數(shù)的兩個(gè)參數(shù)分別是而且必須是resolve
和reject
,它們倆是JS提供的系統(tǒng)函數(shù)尿招,不需要我們自己部署矾柜,我們只要這么固定地寫(xiě)就可以了阱驾;該參數(shù)函數(shù)的執(zhí)行體就是要執(zhí)行的異步任務(wù),并通過(guò)resolve(value)
和reject(error)
固定的寫(xiě)法怪蔑,將異步任務(wù)的執(zhí)行結(jié)果或錯(cuò)誤傳遞出去啊易,執(zhí)行體會(huì)在Promise對(duì)象創(chuàng)建之后立即執(zhí)行。
通過(guò)以上的固定寫(xiě)法饮睬,我們知道resolve
函數(shù)會(huì)在異步操作成功時(shí)觸發(fā),并將異步操作的結(jié)果作為參數(shù)傳遞出去篮奄,這個(gè)函數(shù)的執(zhí)行會(huì)把Promise對(duì)象的狀態(tài)從pending
變?yōu)?code>resolved捆愁;reject
函數(shù)會(huì)在在異步操作失敗時(shí)觸發(fā),并將異步操作的錯(cuò)誤作為參數(shù)傳遞出去窟却,這個(gè)函數(shù)的執(zhí)行會(huì)把Promise對(duì)象的狀態(tài)從pending
變?yōu)?code>rejected昼丑。
- 第二步:使用Promise的
then
方法和catch
方法,為異步任務(wù)添加回調(diào)函數(shù)
在第一步中夸赫,我們并沒(méi)有直接為異步任務(wù)添加回調(diào)函數(shù)菩帝,而僅僅是通過(guò)resolve(value)
和reject(error)
把異步任務(wù)的結(jié)果或錯(cuò)誤傳遞出來(lái)了,現(xiàn)在我們來(lái)為異步任務(wù)添加回調(diào)函數(shù)茬腿。
Promise對(duì)象生成之后呼奢,我們可以通過(guò)它的then
方法,分別指定它變?yōu)?code>resolved狀態(tài)(即異步任務(wù)執(zhí)行成功)和rejected
狀態(tài)(即異步任務(wù)執(zhí)行失斍衅健)后的回調(diào)函數(shù)握础。
promise.then(function (value) {
// success
}, function (error) {
// failure
});
then
方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)會(huì)在異步任務(wù)執(zhí)行成功調(diào)用悴品,第一步傳出來(lái)的value
就能在這里接收到禀综;第二個(gè)回調(diào)函數(shù)會(huì)在異步任務(wù)執(zhí)行失敗調(diào)用,第一步時(shí)傳出來(lái)的error
就能在這里接收到苔严。其中第二個(gè)函數(shù)是可選的定枷,不一定要提供。
同時(shí)then
方法執(zhí)行后的返回值又是一個(gè)新的Promise對(duì)象(注意不是原來(lái)那個(gè)Promise對(duì)象了)届氢,因此可以對(duì)then
方法采用鏈?zhǔn)綄?xiě)法欠窒,這時(shí)上一個(gè)then
方法參數(shù)函數(shù)的執(zhí)行結(jié)果,會(huì)自動(dòng)傳遞給下一個(gè)then
方法的參數(shù)函數(shù)作為參數(shù)悼沈。
promise.then(function (異步任務(wù)的執(zhí)行結(jié)果) {
// ...
return 結(jié)果1;
}).then(function (結(jié)果1) {
// ...
return 結(jié)果2;
}).then(function (結(jié)果2) {
// ...
});
除了then
方法之外贱迟,Promise還有一個(gè)catch
方法,它其實(shí)是.then(null, rejection)
或.then(undefined, rejection)
的別名絮供,可以專(zhuān)門(mén)用來(lái)指定Promise對(duì)象變?yōu)?code>rejected狀態(tài)(即異步任務(wù)執(zhí)行失斠路汀)的回調(diào)函數(shù)。
promise.then(function () {
// ...
return 結(jié)果1;
}).then(function (結(jié)果1) {
// ...
return 結(jié)果2;
}).then(function (結(jié)果2) {
// ...
}).catch(function (error) {
// ...
});
catch
方法可以捕捉它上面所有then
方法的錯(cuò)誤壤靶,使用catch
方法捕捉錯(cuò)誤要比使用then
方法既捕捉成功也捕捉的代碼看起來(lái)清晰明了缚俏。因此我們推薦,使用then
方法提供異步任務(wù)成功的回調(diào),而使用catch
方法提供異步任務(wù)失敗的回調(diào)忧换。
4.fetch
恬惯、AsyncStorage
使用示例
// ProjectRequest.js
/**
* RN提供的fetch方法,是異步的亚茬,它本身就會(huì)返回一個(gè)Promise對(duì)象酪耳。因?yàn)檫@里我們對(duì)它進(jìn)行了封裝使用,所以外面又包了一層Promise刹缝,來(lái)給fetch這個(gè)異步任務(wù)提供回調(diào)碗暗,這樣外界才能拿到它的結(jié)果。
*
* @param url
* @param params
* @returns {Promise<any> | Promise}
*/
static post(url, params) {
return new Promise((resolve, reject) => {
fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(params)
})
.then(response => {
if (response.ok) {
// 請(qǐng)求到的response其實(shí)是一個(gè)Response對(duì)象梢夯,它是一個(gè)很原始的數(shù)據(jù)格式言疗,我們不能直接使用,先獲取它的JSON字符串文本格式
return response.text();
} else {
throw new Error('網(wǎng)絡(luò)請(qǐng)求失斔淘摇噪奄!');
}
})
.then(responseText => {
// 然后把JSON字符串序列化為JS對(duì)象
const responseJSObj = JSON.parse(responseText);
// 把請(qǐng)求成功的數(shù)據(jù)傳遞出去
resolve(responseJSObj);
})
.catch((error) => {
// 把請(qǐng)求失敗的信息傳遞出去
reject(error);
})
})
}
// ThemeDao.js
/**
* 讀取主題色
* RN提供的AsyncStorage,它的讀取和寫(xiě)入操作都是是異步的人乓,只通過(guò)回調(diào)函數(shù)的方式來(lái)告訴我們結(jié)果勤篮。因?yàn)檫@里我們對(duì)它進(jìn)行了封裝使用,所以外面又包了一層Promise撒蟀,來(lái)給AsyncStorage這個(gè)異步任務(wù)提供回調(diào)叙谨,這樣外界才能拿到它的結(jié)果。
*
* @returns {Promise<any> | Promise}
*/
static getThemeColor() {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(THEME_COLOR, (error, value) => {
if (error) {
reject(error);
} else {
if (!value) {// 數(shù)據(jù)庫(kù)中還沒(méi)有存主題色
// 那就搞個(gè)默認(rèn)的主題色
value = AllThemeColor.Default;
// 存起來(lái)
this.saveThemeColor(value);
}
// 傳出去
resolve(value);
}
});
});
}
二. async-await
async-await
的主要作用就是用來(lái)將一個(gè)異步任務(wù)變成同步的保屯。
// 存儲(chǔ)的數(shù)據(jù)為:{'hey': '你好'}
_read() {
console.log(1);
AsyncStorage.getItem('hey', (error, value) => {
if (error) {
console.log('讀取數(shù)據(jù)出錯(cuò):', error);
} else {
console.log(2);
console.log(value);
console.log(3);
}
});
console.log(4);
}
比如上面這串代碼手负,是從數(shù)據(jù)庫(kù)里讀取一些數(shù)據(jù),因?yàn)?code>AsyncStorage.getItem這個(gè)操作是異步的姑尺,所以會(huì)依次輸出1竟终、4、2切蟋、你好统捶、3。
但有時(shí)候柄粹,我們希望確確實(shí)實(shí)從數(shù)據(jù)庫(kù)讀到了數(shù)據(jù)再執(zhí)行后面的操作喘鸟,而不是把操作放到異步操作的回調(diào)里執(zhí)行,此時(shí)就可以用async-await
將一個(gè)異步任務(wù)變成同步的驻右。
async _read() {
console.log(1);
await AsyncStorage.getItem('hey', (error, value) => {
if (error) {
console.log('讀取數(shù)據(jù)出錯(cuò):', error);
} else {
console.log(2);
console.log(value);
console.log(3);
}
});
console.log(4);
}
這樣什黑,代碼在執(zhí)行到await
的地方就會(huì)阻塞住,直到它后面的異步操作執(zhí)行完畢堪夭,才會(huì)執(zhí)行后面的語(yǔ)句愕把,async
只是個(gè)標(biāo)識(shí)符拣凹,沒(méi)什么實(shí)際的意義。這樣會(huì)依次輸出1恨豁、2嚣镜、你好、3橘蜜、4菊匿。