(嫌我話多的可以直接看分割線之后的部分…)
以前高中的時(shí)候自己搗騰博客,一直也就只會(huì)用JQuery寫(xiě)點(diǎn)按鈕事件什么的,連表單提交都沒(méi)寫(xiě)過(guò),后來(lái)誤打誤撞做了前端碼農(nóng)舊覺(jué)得JS的異步模式實(shí)在是太坑爹,當(dāng)你搞清楚異步回調(diào)的時(shí)候,又會(huì)發(fā)現(xiàn)回調(diào)地獄(Callback Hell)太坑爹…
為什么覺(jué)得異步坑爹?看看下面這個(gè)例子:
//以下用setTimeout()模擬一個(gè)請(qǐng)求
function getName() {
return "小白妹妹";
}
function greeting(name) {
console.log("你好," + name);
}
//生成一個(gè)0到1000的隨機(jī)數(shù),模擬不確定的等待時(shí)間0-1秒
var randomTime = function () {
return Math.random() * 1000;
};
var name = "";
//1秒之內(nèi)給name賦值為 小白妹妹,但不知道具體時(shí)間
setTimeout(function () {
name = getName();
}, randomTime());
greeting(name); //以為得到name之后就可以開(kāi)心的去打招呼啦,然而…
這是錯(cuò)的!這是錯(cuò)的!這是錯(cuò)的!
可能很多新手都犯過(guò)這個(gè)錯(cuò)誤,錯(cuò)的時(shí)候還不知道為啥錯(cuò)了…深究原因的話跟JS的機(jī)制有關(guān),長(zhǎng)篇大論的就不在這里多說(shuō)了(其實(shí)是我說(shuō)不清楚…)
再有點(diǎn)經(jīng)驗(yàn),就會(huì)知道,應(yīng)該把greeting(name)寫(xiě)在回調(diào)函數(shù)里,這樣就能保證在得到數(shù)據(jù)之后才運(yùn)行greeting()函數(shù),于是…當(dāng)你有多個(gè)請(qǐng)求并且之間是有這種依賴(lài)關(guān)系的時(shí)候,可怕的回調(diào)地獄就出現(xiàn)了!
而解決回調(diào)地獄其中一個(gè)很優(yōu)雅的方法,就是使用傳說(shuō)中的promise!
關(guān)于promise這個(gè)概念我前前后后看了有一年了,周?chē)矝](méi)有認(rèn)識(shí)的人可以給我講,只能自己看看網(wǎng)上的文章,然而…并沒(méi)有什么卵用…網(wǎng)上的文章都是眾說(shuō)紛紜,有一上來(lái)就defer的[1],也有一上來(lái)就說(shuō)如果你還在用defer你就理解錯(cuò)了Promise的[2],就所以我到現(xiàn)在也不知道究竟什么樣的理解才是對(duì)的…
關(guān)于Promise以及A+規(guī)范就不在此詳述了,那些概念的東東,我也還沒(méi)完全理解。我想做的是讓跟我一樣的小白都能明白Promise最基本的用法。
舉個(gè)栗子…
有一個(gè)第三方提供的API,訪問(wèn)API能夠得到一些用戶(hù)數(shù)據(jù)(每訪問(wèn)一次得到一頁(yè),假設(shè)由于某些限制一頁(yè)只返回3個(gè)用戶(hù))以及下一頁(yè)的index;除了第一頁(yè)之外,其他的頁(yè)面都需要下一頁(yè)的index參數(shù)才能訪問(wèn)到。(先不管這個(gè)API設(shè)計(jì)的合不合理…Facebook就有這樣的API)
http://example.com/user -->訪問(wèn)第一頁(yè)的用戶(hù)數(shù)據(jù)
http://example.com/user?next=xzmca -->訪問(wèn)第二頁(yè)的用戶(hù)數(shù)據(jù)
請(qǐng)求第一頁(yè)時(shí)返回結(jié)果如下:
{
"items" : [{
"name" : "小白妹妹",
"age" : 10
},
{...}, {...}],
"nextPage" : "xzmca",
"lastPage" : null
}
你可能會(huì)有這樣的需求:你的APP一次需要顯示6個(gè)甚至更多個(gè)的用戶(hù)數(shù)據(jù)。而根據(jù)之前的API,一次只能拿到3個(gè)數(shù)據(jù),那么就只能發(fā)出兩次請(qǐng)求,并且第二次請(qǐng)求依賴(lài)于第一次請(qǐng)求的結(jié)果,由于異步的原因我們并不知道第一個(gè)請(qǐng)求什么時(shí)候才完成,而我最初入坑時(shí)是讓程序發(fā)完第一個(gè)請(qǐng)求后強(qiáng)制等2秒再發(fā)第二個(gè)請(qǐng)求我會(huì)告訴你們嗎…
下面為了方便,使用setTimeout()函數(shù)和bluebird庫(kù)進(jìn)行說(shuō)明:
//生成一個(gè)0到3000的隨機(jī)數(shù),模擬不確定的等待時(shí)間0-3秒
var randomTime = function () {
return Math.random() * 3000;
};
//只考慮最簡(jiǎn)單的情況promise被resolve,暫時(shí)不考慮promise被reject的情況
function req1() {
return new Promise(function (resolve) {
//使用setTimeout()來(lái)模擬發(fā)送請(qǐng)求,data為請(qǐng)求得到的數(shù)據(jù)
setTimeout(function () {
var data = {
"items": [{
"name": "小白妹妹",
"age": 10
}, {
"name": "小白",
"age": 100
}, {
"name": "妹妹",
"age": 111
}],
"nextPage": "asdfa",
"lastPage": null
};
resolve(data);
console.log("請(qǐng)求1完成");
}, randomTime());
})
}
function req2(dataFromReq1) {
return new Promise(function (resolve) {
setTimeout(function () {
var data = {
"items": [{
"name": "小黑姐姐",
"age": 20
}, {
"name": "小黑",
"age": 233
}, {
"name": "姐姐",
"age": 250
}],
"nextPage": "gwdfx",
"lastPage": "asdfa"
};
console.log(dataFromReq1);
var finalUserData = dataFromReq1.items.concat(data.items); //將兩次得到的用戶(hù)數(shù)據(jù)合并
resolve(finalUserData);
console.log("請(qǐng)求2完成");
}, randomTime());
})
}
//讓數(shù)據(jù)在promise鏈上歡快的傳遞吧~
req1().then(req2).then(function (data) {
console.log(data);
});
Q: 什么時(shí)候需要返回一個(gè)promise呢?
A: 當(dāng)你的需求邏輯是,XXX的執(zhí)行需要依賴(lài)OOO的結(jié)果,此時(shí)OOO就應(yīng)該返回一個(gè)promise
Q: 為什么req1要打括號(hào),而req2不打括號(hào)?
A:這個(gè)我也沒(méi)太搞清楚XD,我的理解是,req1()打括號(hào)執(zhí)行后才會(huì)返回promise,不打括號(hào)就只是一個(gè)沒(méi)有執(zhí)行的函數(shù),req2不打括號(hào)是因?yàn)閠hen的入?yún)⒅荒苁且粋€(gè)函數(shù),如果打了括號(hào)執(zhí)行后就不是函數(shù)了。
Q: 最后一個(gè)then的function(data) data是哪里來(lái)的?
A:req2的定義中,有一句resolve(finalUserData),在Promise Chain中,每個(gè)then的入?yún)⒌娜雲(yún)⒁簿褪莊unction(data)中的data都是由前一個(gè)promise resolve時(shí)傳遞而來(lái)的