web前端: 解決“先發(fā)起請(qǐng)求后收到響應(yīng)”問題

前言: 當(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)系,如侵必刪。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • title: promise總結(jié) 總結(jié)在前 前言 下文類似 Promise#then、Promise#resolv...
    JyLie閱讀 12,419評(píng)論 1 21
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,674評(píng)論 1 32
  • $HTML, HTTP,web綜合問題 1、前端需要注意哪些SEO 2、 的title和alt有什么區(qū)別 3、HT...
    Hebborn_hb閱讀 4,783評(píng)論 0 20
  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調(diào)來表達(dá)異步和管理并發(fā)時(shí)的兩種主要不足:缺...
    purple_force閱讀 2,252評(píng)論 0 4
  • 11月26日早晨凈重:57.1公里 11月26日飲食: 早餐:一碗地瓜粥我,一個(gè)雞蛋,200毫升的蛋白粉 午餐:兩...
    若年華閱讀 318評(píng)論 2 2

友情鏈接更多精彩內(nèi)容