大家在js編程中肯定遇到過(guò)異步問(wèn)題,實(shí)踐中大家在異步問(wèn)題上也不斷提出新的解決方案馋艺,這里就梳理一下異步解決方案嚎卫。
回調(diào)函數(shù)
回調(diào)函數(shù)是最原始的解決方案嘉栓,邏輯上很容易理解⊥刂睿看個(gè)例子:
function f1(value, callback) {
console.log('f1');
setTimeout(
() => {
value = setValue('f2');
callback.call(this, value);
}
, 1000
);
}
function f2(value){
console.log(value);
}
function setValue(value) {
return value;
}
let value;
f1(value, f2);
這里的
f2
需要f1
延時(shí)1s完成value
的重新賦值后再打印value
侵佃,比較簡(jiǎn)單的處理就是把f2
作為f1
的回調(diào)函數(shù)。
這是歷史最悠久的方案
- 優(yōu)點(diǎn):兼容性好
- 缺點(diǎn):當(dāng)回調(diào)函數(shù)不斷嵌套時(shí)奠支,代碼會(huì)橫向發(fā)展馋辈,形成“意大利面”(Italian noodles)代碼
Promise對(duì)象
隨著歷史的發(fā)展,在前端社區(qū)的某些庫(kù)中有人提出了Promise
方案倍谜,后來(lái)這個(gè)方案也被寫入官方標(biāo)準(zhǔn)迈螟。本文不再談各種前端庫(kù)中Promise
的實(shí)現(xiàn),只談目前ES規(guī)范
中的Promise
尔崔。
幾個(gè)要點(diǎn):
-
Promise
是一個(gè)原生對(duì)象答毫,我們使用Promise
對(duì)象是利用它的API。 -
Promise
對(duì)象的異步操作有三種狀態(tài):pending
(進(jìn)行中)季春、fulfilled
(已成功)和rejected
(已失斚绰А)≡嘏回調(diào)函數(shù)只在fulfilled
(已成功)和rejected
(已失斣拍础)狀態(tài)下觸發(fā)。 -
Promise
對(duì)象的實(shí)例可以調(diào)用then
方法指定不同狀態(tài)的回調(diào)函數(shù)(或catch
方法注冊(cè)rejected
(已失斢罟ァ)狀態(tài)的回調(diào)函數(shù))
使用Promise
對(duì)象重新實(shí)現(xiàn)上文的例子:
let value;
function f1() {
console.log('f1');
const promise = new Promise((resolve, reject) => {
setTimeout(
() => {
let value = setValue('f2');
if (!!value) {
resolve(value);
} else {
reject('error');
}
}
, 1000);
});
return promise;
}
function f2(value){
console.log(value);
}
function setValue(value) {
return value;
}
f1()
.then(
f2,
(error) => console.log(error)
);
可以看到函數(shù)
f1
返回了一個(gè)Promise
對(duì)象的實(shí)例惫叛,這個(gè)實(shí)例就具有.then/.catch
等方法注冊(cè)回調(diào)函數(shù)。再看一個(gè)使用
Promise
實(shí)現(xiàn)的AJAX
封裝
function fetchRemote(url, method='GET', param=null) {
const promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader("Accept", "application/json");
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4){
return;
}
if (xhr.status === 200 || xhr.status === 304) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
};
xhr.send(param);
});
return promise;
}
// 調(diào)用
fetchRemote(url)
.then(
(response) => {
todo...
}
)
.catch(
(error) => {
todo...
}
)
這里提一個(gè)小細(xì)節(jié): 請(qǐng)注意XMLHttpRequest對(duì)象的實(shí)例具有實(shí)例方法onreadystatechange()
尺碰,實(shí)際上這是一個(gè)注冊(cè)回調(diào)函數(shù)的api挣棕,每一次XMLHttpRequest實(shí)例對(duì)象的readyState/status
被改變時(shí)译隘,回調(diào)函數(shù)都會(huì)被異步觸發(fā)
此外,Promise
對(duì)象也提供了一些新API適應(yīng)不同場(chǎng)景洛心,例如.race/.all
固耘。
// Promise.race() 適用于等待多個(gè)promise第一個(gè)完成的場(chǎng)景
Promise.race(task1, task2, task3...).then().catch();
// Promise.all() 適用于等待多個(gè)promise同時(shí)完成的場(chǎng)景
Promise.all(task1, task2, task3...).then().catch();
Generator函數(shù)
學(xué)習(xí)過(guò)Generator函數(shù)
的同學(xué)應(yīng)該知道厅目,Generator函數(shù)
最大的特點(diǎn)就是不會(huì)順序執(zhí)行到底,遇到yield
語(yǔ)句時(shí)會(huì)暫停法严,直到調(diào)用next
方法才會(huì)繼續(xù)執(zhí)行。顯然深啤,這種特性很適合異步場(chǎng)景。
還是實(shí)現(xiàn)最初的小例子:
const setValue = value => value;
let value;
function* f1() {
console.log('f1');
yield new Promise((resolve, reject) => {
setTimeout(() => {
value = setValue('f2');
if (!!value) {
resolve(value);
} else {
reject('error');
}
}, 1000);
});
}
function f2 (value) {
console.log(value);
}
let g = f1();
g.next().value
.then(f2)
.catch((error) => {console.error(error);});
再看一個(gè)遠(yuǎn)程請(qǐng)求的例子:
function* fetchData(url) {
const data = yield fetch(url);
yield handler(data);
}
const g = fetchData('http://url/api');
g.next();
g.next();
可以看到溯街,通過(guò)使用Generator函數(shù)
的屬性,異步邏輯變得非常簡(jiǎn)單呈昔,只需要在異步等待的函數(shù)處“暫突拥龋”即可。這種語(yǔ)法在redux-saga
的庫(kù)中有很好的應(yīng)用堤尾。
async函數(shù)
async函數(shù)
顧名思義肝劲,就是為解決異步場(chǎng)景而生的。
async函數(shù)
返回的是一個(gè)Promise
對(duì)象郭宝,所以async函數(shù)
返回的結(jié)果可以調(diào)用.then()
/.catch()
方法辞槐,本質(zhì)上和promise對(duì)象
用法類似,只是有了語(yǔ)法糖書寫更簡(jiǎn)便剩蟀。
還是這個(gè)熟悉的老例子:
const setValue = value => value;
let value;
function f1() {
console.log('f1');
return new Promise((resolve, reject) => {
setTimeout(() => {
value = setValue(null);
if (!!value) {
resolve(value);
} else {
reject('error');
}
}, 1000);
});
}
function f2 (value) {
console.log(value);
}
async function fn() {
return await f1();
}
fn()
.then(
(value) => {console.log(value);}
)
.catch(
(error) => console.error(error)
);
簡(jiǎn)單解釋一下催蝗,
async
和await
需要互相配合,await
只能在async函數(shù)
中使用育特,await
等的是promise
對(duì)象丙号,如果await
得到的不是promise
對(duì)象則會(huì)把普通對(duì)象直接作為promise
的value
并更新?tīng)顟B(tài)為resolved
。可以很明顯看出缰冤,一般情況下
async函數(shù)
不具有優(yōu)勢(shì)犬缨,它的優(yōu)勢(shì)在處理then
鏈。看個(gè)例子:
async function manycbs() {
const arg1 = 'xxx';
const arg2 = await step1(arg1);
const arg3 = await step2(arg1, arg2);
const result = await step3(arg1, arg2, arg3);
console.log(result);
}
manycbs();
這樣就可以看出async函數(shù)
可以把異步寫得像同步一樣棉浸。