前言: 當(dāng)我們發(fā)送請(qǐng)求時(shí),由于請(qǐng)求是異步的,前一次發(fā)起的請(qǐng)求不會(huì)阻塞后一次請(qǐng)求的發(fā)起,順理成章地,前一次請(qǐng)求也未必會(huì)比后一次請(qǐng)求先返回。于是導(dǎo)致的直接后果就是后一次請(qǐng)求響應(yīng)的數(shù)據(jù)可能先渲染,待前一次請(qǐng)求響應(yīng)時(shí),直接覆蓋了后一次請(qǐng)求的渲染結(jié)果。這可不是我們所期望看到的。
舉例
前言中所描述的問題,用代碼來表示的話,就是:
var result;
// A function to simulate async request delay
var request = function (msec, mockedData, callback) {
setTimeout(function () {
callback(mockedData);
}, msec);
};
var sample = [
{ msec: 200, data: 'stale' },
{ msec: 100, data: 'fresh' }
];
// 模擬`先發(fā)起請(qǐng)求后響應(yīng)`的情景
sample.forEach(function (item) {
request(item.msec, item.data, function (resp) {
result = resp;
});
});
// wait 1s, we inspect the `result`
setTimeout(function () {
// we expect `result` to be `fresh`, but it is `stale`
console.log(result); // => `stale`
}, 1000);
代碼中后一次執(zhí)行的異步操作覆蓋了前一次執(zhí)行的,最后得到的result為'stale',顯然是錯(cuò)誤的。
解決方案
- 第一種方法
在前一次未返回結(jié)果時(shí),禁止發(fā)送下一次請(qǐng)求,也就是把異步的請(qǐng)求強(qiáng)行改為同步的,這顯然不是最好的解決方案。 - 第二種方法
在每次響應(yīng)后且在渲染之前,判斷當(dāng)前響應(yīng)是不是對(duì)應(yīng)最新一次請(qǐng)求的。是,則渲染;不是,則不渲染。
這種思路最容易想到的實(shí)現(xiàn)就是使用全局變量標(biāo)記最新請(qǐng)求,局部變量標(biāo)記當(dāng)前請(qǐng)求,然后在響應(yīng)回調(diào)中判斷局部變量的值是否和全局變量的值一樣。如果不一樣,忽略響應(yīng)結(jié)果;如果一樣,我們可以斷言這是最新請(qǐng)求的響應(yīng),直接渲染。
以下是這種思路針對(duì)上面代碼的改寫:
var result;
var globalMark = 0;
// A function to simulate async request delay
var request = function (msec, mockedData, callback) {
setTimeout(function () {
callback(mockedData);
}, msec);
};
var sample = [
{ msec: 200, data: 'stale' },
{ msec: 100, data: 'fresh' }
];
// 模擬`先發(fā)起請(qǐng)求后響應(yīng)`的情景
sample.forEach(function (item) {
var localMark = ++globalMark;
request(item.msec, item.data, function (resp) {
if (localMark !== globalMark) {
return;
}
result = resp;
});
});
// wait 1s, we inspect `result`
setTimeout(function () {
// now our `result` is `fresh`, just as expected
console.log(result); // => `fresh`
}, 1000);
因?yàn)槲覀兺ǔJ怯胮romise的異步請(qǐng)求來進(jìn)行調(diào)用接口的,所以我們可以改為promise的版本:
var result;
var globalMark = 0;
// 將異步函數(shù)轉(zhuǎn)換為promise式
var promisify = function (fn) {
// Is `fn` thenable?
return fn.then ? fn : function () {
var args = Array.from(arguments);
return new Promise(resolve => void fn(...args.concat(resolve)));
};
};
// 將request方法promise化
// A function to simulate async request delay
var request = function (msec, mockedData, callback) {
setTimeout(function () {
callback(mockedData);
}, msec);
};
// decorate `request` with `promisify`
request = promisify(request);
var sample = [
{ msec: 200, data: 'stale' },
{ msec: 100, data: 'fresh' }
];
// 模擬`先發(fā)起請(qǐng)求后響應(yīng)`的情景
sample.forEach(function (item) {
var localMark = ++globalMark;
request(item.msec, item.data).then(function (resp) {
if (localMark !== globalMark) {
return;
}
result = resp;
});
});
// wait 1s, we inspect `result`
setTimeout(function () {
// now our `result` is `fresh`, just as expected
console.log(result); // => `fresh`
}, 1000);
這種思路可以正常工作,而且簡單直觀。但是當(dāng)我們要處理大量這類請(qǐng)求問題時(shí),這類重復(fù)邏輯的代碼將散落在各個(gè)地方,不是很優(yōu)雅。問題不在于思路上,而在于實(shí)現(xiàn)。我們需要在源頭(請(qǐng)求)處規(guī)避(控制)問題,而不是到結(jié)果(響應(yīng))處解決問題。
- 第三種方法
我們可以對(duì)原有promisify后得到的方法再進(jìn)行一層裝飾,在請(qǐng)求處就控制問題,而不是在得到結(jié)果后再進(jìn)行解決問題。
var result;
// 將promise再經(jīng)過一層處理
var mutePrior = function (promisifiedFunc) {
var registry = [0];
return function () {
var promise = promisifiedFunc(...arguments);
registry.push(promise);
return new Promise(function (...actions) {
var proxyCallbacks = actions.map(action => function (result) {
if (registry.indexOf(promise) === registry.length - 1) {
action(result);
registry.length = 1;
}
});
promise.then(...proxyCallbacks);
});
};
};
// 將異步函數(shù)轉(zhuǎn)換為promise式
var promisify = function (fn) {
// Is `fn` thenable?
return fn.then ? fn : function () {
var args = Array.from(arguments);
return new Promise(resolve => void fn(...args.concat(resolve)));
};
};
// 將request方法promise化
// A function to simulate async request delay
var request = function (msec, mockedData, callback) {
setTimeout(function () {
callback(mockedData);
}, msec);
};
// decorate `request` with `promisify`
request = mutePrior(promisify(request));
var sample = [
{ msec: 200, data: 'stale' },
{ msec: 100, data: 'fresh' }
];
// 模擬`先發(fā)起請(qǐng)求后響應(yīng)`的情景
sample.forEach(function (item) {
request(item.msec, item.data).then(function (resp) {
result = resp;
});
});
// wait 1s, we inspect `result`
setTimeout(function () {
// now our `result` is `fresh`, just as expected
console.log(result); // => `fresh`
}, 1000);
這樣就可以在請(qǐng)求的源頭解決這個(gè)問題O(∩_∩)O哈哈~。
參考文章: http://myunlessor.github.io/blog/2015/12/19/promise-solve-continuous-request-and-respone-not-in-order-problem/
本文僅供交流學(xué)習(xí)使用,有疑問請(qǐng)與作者聯(lián)系,如侵必刪。