前言: 當我們發送請求時,由於請求是異步的,前一次發起的請求不會阻塞後一次請求的發起,順理成章地,前一次請求也未必會比後一次請求先返回。於是導致的直接後果就是後一次請求響應的數據可能先渲染,待前一次請求響應時,直接覆蓋了後一次請求的渲染結果。這可不是我們所期望看到的。
舉例
前言中所描述的問題,用代碼來表示的話,就是:
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' }
];
// 模擬`先發起請求後響應`的情景
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);
代碼中後一次執行的異步操作覆蓋了前一次執行的,最後得到的result爲'stale',顯然是錯誤的。
解決方案
- 第一種方法
在前一次未返回結果時,禁止發送下一次請求,也就是把異步的請求強行改爲同步的,這顯然不是最好的解決方案。 - 第二種方法
在每次響應後且在渲染之前,判斷當前響應是不是對應最新一次請求的。是,則渲染;不是,則不渲染。
這種思路最容易想到的實現就是使用全局變量
標記最新請求,局部變量
標記當前請求,然後在響應回調中判斷局部變量
的值是否和全局變量
的值一樣。如果不一樣,忽略響應結果;如果一樣,我們可以斷言這是最新請求的響應,直接渲染。
以下是這種思路針對上面代碼的改寫:
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' }
];
// 模擬`先發起請求後響應`的情景
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);
因爲我們通常是用promise的異步請求來進行調用接口的,所以我們可以改爲promise的版本:
var result;
var globalMark = 0;
// 將異步函數轉換爲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' }
];
// 模擬`先發起請求後響應`的情景
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);
這種思路可以正常工作,而且簡單直觀。但是當我們要處理大量這類請求問題時,這類重複邏輯的代碼將散落在各個地方,不是很優雅。問題不在於思路上,而在於實現。我們需要在源頭(請求)處規避(控制)問題,而不是到結果(響應)處解決問題。
- 第三種方法
我們可以對原有promisify後得到的方法再進行一層裝飾,在請求處就控制問題,而不是在得到結果後再進行解決問題。
var result;
// 將promise再經過一層處理
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);
});
};
};
// 將異步函數轉換爲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' }
];
// 模擬`先發起請求後響應`的情景
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);
這樣就可以在請求的源頭解決這個問題O(∩_∩)O哈哈~。
參考文章: http://myunlessor.github.io/blog/2015/12/19/promise-solve-continuous-request-and-respone-not-in-order-problem/
本文僅供交流學習使用,有疑問請與作者聯繫,如侵必刪。