如果是單個ajax請求,不帶有接口錢數據相互依賴的,其實怎么請求都沒關系.
jQuery,axios,vue-resource,XMLHttpRequest
但一點有了接口間數據的相互依賴,那么問題就來了.
由于,每一個請求都是獨立的,且回調的時機不確定,為了保證請求接口數據返回的過程可控和相互依賴關系,可能需要費點周折.
1. ajax callback
$.get('url',function(data){
$.get('url2' + data.id,function(data2){
$.get('url3' + data2.id,func(data){
// 拿到最終數據
})
})
})
使用最原始的方案,ajax callback 這種 回調函數嵌套的方式,在請求數據上面是沒有問題的.在一個接口請求完畢之后,拿到數據,在接著請求下一個接口,很好理解,也很好書寫.
問題在于:
當接口依賴多了之后,整個代碼的嵌套層級就會變多,代碼應該的豎向發(fā)展,變成了橫向.
這樣可能會導致代碼結構不清晰,不便于后期的維護.
2. Promise
function pget(url) {
return new Promise(function(reslove,reject){
$.get(url,function(data){
if (data.err) reject(data.err)
resolve(data)
})
})
}
pget('url')
.then(res=>{
return pget('url2' + res.id)
})
.then(res=>{
return pget('url3' + res.id)
})
.then(res=>{
// 拿到最終數據
})
使用 then 確實能把callback 那種橫向的趨勢變成更加符合代碼風格的縱向.
問題在于:
then 太多了,且語義化不強,前面的
then也許還知道是干什么的,到了后面可能就懵逼了.
3. Generator
function *gen() {
let data1 = yield $.ajax('url')
let data2 = yield $.ajax('url2' + data1.id)
yield $.ajax('url3') // 注意,在jQuery v3.0+版本,$.ajax() 支持了 promise
}
let g = gen()
g.next().value
.then(res=>{
g.next(res.id).value
.then(res=>{
g.next(res.id).value
.then(res=>{
// 拿到數據了,該干嘛干嘛.
})
})
})
問題在于
- 相比
$.ajax()函數嵌套,變的更加復雜。 - 相比
promise屬性變的更多。(.next().value)
三種方式.
- 第一種 ajax callback 存在回調函數,結構是橫向發(fā)展的.
- promise 確實結構變清晰了,在每一次的
reslove返回一個新的promise,把這種接口依賴的操作用then縱向連接了起來. - generator 就有點扯淡了,不光需要
next()還要.value,更加扯淡的是,嵌套層級和$.ajax()沒有區(qū)別,甚至于比$.ajax()寫的代碼更多了.
我們所希望的
不管是 $.ajax() callback 還是 promise then 或者是 generator .next().
我們都希望都以同步的方式去寫異步代碼.
let data = 異步請求數據(url)
let data2 = 異步請求數據(url2 + data.id)
let data3 = 異步請求數據(url3 + data2.id)
console.log(data,data1,data2)
由于 generator可以一次一次的拿到 promise,并且可以暫停執(zhí)行.
可以根據 generator 的這個特性來實現以同步代碼的寫法來執(zhí)行異步任務
function readFilePromise(path) {
return new Promise(function (reslove, reject) {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err)
reslove(data)
})
})
}
function* gen2() {
let data1 = yield readFilePromise('./1.txt')
console.log(`data1:${data1}`)
let data2 = yield readFilePromise('./2.txt')
console.log(`data2:${data2}`)
}
此段代碼,正常情況下,每一次 next() 拿到了的對象 .value 都是一個 promise.
核心思想是,我們等待 promse 對象,執(zhí)行完畢了,在繼續(xù)執(zhí)行下一個 yield .
function genRunner(gen) {
let g = gen() // 拿到迭代器指針
function next(data) {
let nextObj = g.next(data)
if (!nextObj.done) { // 如果迭代沒有完成
// 等待當前這個 promise執(zhí)行完成了.
nextObj.value.then(res=>{
// 繼續(xù)下一次的迭代.并把結果傳遞給當前的next()方法.
next(res)
})
}
}
next() // 第一此調用第一個 yield ,第一個yield沒辦法接受參數.
}
測試一下:
genRunner(gen2)
結果:
data1:1111111111
data2:22222222222
所以,我們可以利用 generator & runner 的工具,在 generator 中像寫同步代碼那樣書寫異步代碼.
genRunner 函數到底干了些什么事情?
- 首先,
generator我們是可以手動調用next()一步步執(zhí)行的. -
genRunner首選拿到 迭代器指針,指向第一個next()返回的obj - 根據這個
obj.done判斷迭代器是否遍歷完畢.-
如果沒有遍歷完畢,
- 就先拿到這個
obj.value,也就是promise對象,執(zhí)行它的then. - 等待這個
promise的then執(zhí)行完畢之后,再次手動調用next()
- 就先拿到這個
如果執(zhí)行完畢了,就什么也不做.
-
常用的工具還有 co
npm i co
co(gen2)
data1:1111111111
data2:2222222222
問題在于
如果我們非要使用
generator發(fā)送異步請求的話,那么為了不寫惡心的嵌套代碼,就需要借助第三方co,runner這樣的插件.(或者自己寫一個)
使用 ES7 推出的 async / await
function readFilePromise(path) {
return new Promise(function (reslove, reject) {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err)
reslove(data)
})
})
}
async function readFile() {
let res1 = await readFilePromise('./1.txt')
let res2 = await readFilePromise('./2.txt')
}
結果:
data1:1111111111
data2:2222222222
async & await 基本是個語法糖,它結合了 generator & 類似 runner | co 的功能,能讓我們很舒服的用同步代碼的方式寫異步代碼.