如何处理多个浏览器脚本,对后端服务进行相同的调用

时间:2015-01-23 10:09:13

标签: javascript asynchronous xmlhttprequest promise es6-promise

我有一个网页,其中不同部分都需要相同的后端数据。每个都是隔离的,因此它们最终最终会对后端进行相同的调用。

当一台服务器正在进行并且由同一网页上的不同代码启动时,避免调用Web服务器的最佳方法是什么?

这是一个例子。我将使用setTimeout来模拟异步调用。

我们假设有一个异步函数返回联系人列表,在这个例子中基本上是一个简单的字符串数组:

var getContacts = function() {
  log('Calling back-end to get contact list.');
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      log('New data received from back-end.');
      resolve(["Mary","Frank","Klaus"]);
    }, 3000);
  });
};

现在,让我们创建三个独立的函数,每个函数为不同的目的调用上述函数。

转出联系人​​列表:

var dumpContacts = function() {
  getContacts().then(function(contacts) {
    for( var i = 0; i < contacts.length; i++ ) {
      log( "Contact " + (i + 1) + ": " + contacts[i] );
    }
  });
};

确定特定联系人是否在列表中:

var contactExists = function(contactName) {
  return getContacts().then(function(contacts) {
    return contacts.indexOf(contactName) >= 0 ? true : false;
  });
};

获取第一个联系人的姓名:

var getFirstContact = function() {
  return getContacts().then(function(contacts) {
    if ( contacts.length > 0 ) {
      return contacts[0];
    }
  });
};

以下是一些使用这三个函数的示例代码:

// Show all contacts
dumpContacts();

// Does contact 'Jane' exist?
contactExists("Jane").then(function(exists){
  log("Contact 'Jane' exist: " + exists);
});

getFirstContact().then(function(firstContact){
  log("first contact: " + firstContact);
});

以上例程使用全局log()函数。可以使用console.log()代替。上面的log()函数登录到浏览器窗口,实现如下:

function log() {
  var args = Array.prototype.slice.call(arguments).join(", ");
  console.log(args);
  var output = document.getElementById('output');
  output.innerHTML += args + "<br/>";
}

并在html中要求以下内容:

<div id='output'><br/></div>

运行上述代码后,您将看到:

Calling back-end to get contact list.

New data received from back-end.

三次,这是不必要的。

如何解决这个问题?

这个样本在Plunker上可以执行: http://plnkr.co/edit/6ysbNTf1lSf5b7L3sJxQ?p=preview

3 个答案:

答案 0 :(得分:2)

如果希望减少对后端的不必要呼叫的数量,那么请坚持承诺,当它仍未解决时,将其返回给新呼叫,而不是向后面发出另一个呼叫末端。

这是一个例程,它将异步函数(一个返回一个promise)转换为一个只在promise未被解析时才被调用的函数。

var makeThrottleFunction = function (asyncFunction) {
  var currentPromiser = getPromise = function() {
    var promise = new Promise(function(resolve, reject) {
      asyncFunction().then(function(value) {
        resolve(value);
        currentPromiser = getPromise;
      }).catch(function(e) {
        reject(e);
        currentPromiser = getPromise;
      });
    });

    currentPromiser = function() {
      return promise;
    };

    return promise;
  }

  return function () {
    return currentPromiser();
  };
};

在您的日常工作中,您可以像这样转换getContacts

var getContacts = makeThrottleFunction(getContacts);

或直接传递整个函数体。

请记住,这只适用于后端的无参数调用。

示例plunker代码:http://plnkr.co/edit/4JTtHmFTZmiHugWNnlo9?p=preview

答案 1 :(得分:2)

只需将结果缓存在进行调用的函数中:

function cache(promiseReturningFn){
    var cachedVal = null;  // start without cached value
    function cached(){
        if(cachedVal) return cachedVal; // prefer cached result
        cachedVal = promiseReturningFn.apply(this, arguments); // delegate
        return cachedVal; // after we saved it, return it
    }
    cached.flush = function(){ cachedVal = undefined; };
    return cached;
}

这有一个失败的警告,实际结果是null,但否则它可以很好地完成工作。

你现在可以缓存任何promise返回函数 - 上面的版本只缓存忽略参数 - 但是你也可以构建一个类似的基于不同参数的Map和缓存 - 但是让我们关注你的用例。

var getContactsCached = cache(getContacts);

getContactsCached();
getContactsCached();
getContactsCached(); // only one async call ever made

缓存方法实际上甚至与promises无关 - 它只需要一个函数并缓存其结果 - 你可以将它用于任何事情。事实上,如果您使用像下划线这样的库,则可以使用_.memoize为您完成此操作。

答案 2 :(得分:-4)

修改,更新

删除&#34;嵌套&#34; ternary模式;添加

  • a)dfd.err().catch()来处理传递给Promise.reject(/* reason ? */)的{​​{1}} arguments;
  • b)dfd.fn()中的args === ""来处理dfd.process():空""作为String传递给argument
  • c)取代&#34;链接&#34; dfd.fn()要求.then()

原住民then.apply(dfd.promise, [contactExists, getFirstContact])作为Error()传递:argumentdfd.fn(new Error("error"))范围处理; global仍会返回dfd.fn()。可以在dfd.promise之前或之前进行调整,以及早期&#34;在dfd.process()或将Error传递给Error;根据要求。未在dfd.err()以下处理。

尝试

js

var dfd = {
  // set `active` : `false`
  "active": false,
  // set `promise`: `undefined`
  "promise": void 0,
  // process `arguments`, if any, passed to `dfd.fn`
  "process": function process(args) {
    // return `Function` call, `arguments`, 
    // or "current" `dfd.promise`;
    // were `args`:`arguments` passed ?
    // handle `""` empty `String` passed as `args`
    return args === "" || !!args
             // if `args`:`Function`, call `args` with `this`:`dfd`,
             // or, set `args` as `value`, `reason`
             // of "next" `dfd.promise`
             // return "next" `dfd.promise` 
           ? args instanceof Function && args.call(this) || args 
             // set `dfd.active`:`false`
             // when "current" `dfd.promise`:`Promise` `fulfilled`,
             // return "current" `dfd.promise`
           : this.active = true && this.promise
  },
  // handle `fulfilled` `Promise.reject(/* `reason` ? */)`,
  // passed as `args` to `dfd.fn`
  "err": function err(e) {
    // notify , log `reason`:`Promise.reject(/* `reason` ? */)`, if any,
    // or, log `undefined` , if no `reason` passed: `Promise.reject()` 
    console.log("rejected `Promise` reason:", e || void 0);
  },
  // do stuff
  "fn": function fn(args /* , do other stuff */) {
    // set `_dfd` : `this` : `dfd` object
    var _dfd = this;
    // if "current" `dfd.promise`:`Promise` processing,
    // wait for `fulfilled` `dfd.promise`;
    // return `dfd.promise`
    _dfd.promise = !_dfd.active
                     // set, reset `dfd.promise`
                     // process call to `dfd.async`;
                     // `args`:`arguments` passed to `dfd.fn` ?,
                     // if `args` passed, are `args` `function` ?,
                     // if `args` `function`, call `args` with
                     // `this`:`dfd`; 
                     // or, return `args`
                   ? _dfd.process(args)
                     // if `_dfd.active`, `_dfd.promise` defined,
                     // return "current" `_dfd.promise`
                   : _dfd.promise.then(function(deferred) {
                        // `deferred`:`_dfd.promise`
                        // do stuff with `deferred`,
                        // do other stuff,
                        // return "current", "next" `deferred`
                        return deferred
                      })
                      // handle `args`:`fulfilled`,
                      // `Promise.reject(/* `reason` ? */)`
                      .catch(_dfd.err);
    return Promise.resolve(_dfd.promise).then(function(data) {
        // `data`:`undefined`, `_dfd.promise`
        // set `_dfd.active`:`false`,
        // return `value` of "current", "next" `_dfd.promise`
        _dfd.active = false;
        return data
      })
      // handle `fulfilled` `Promise.reject(/* `reason` ? */), 
      // if reaches here ?
      .catch(_dfd.err)
  }
};

&#13;
&#13;
    function log() {
        var args = Array.prototype.slice.call(arguments).join(", ");
        console.log(args);
        var output = document.getElementById('output');
        output.innerHTML += args + "<br/>";
    };

    var dumpContacts = function () {
        log('Calling back-end to get contact list.');
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                log('New data received from back-end.');
                resolve(["Mary", "Frank", "Klaus"]);
            }, 3000);
        });
    };

    var getContacts = function () {
        return dfd.async().then(function (contacts) {
            for (var i = 0; i < contacts.length; i++) {
                log("Contact " + (i + 1) + ": " + contacts[i]);
            }
        });
    };

    var contactExists = function (contactName) {
        return dfd.async().then(function (contacts) {
            return contacts.indexOf(contactName) >= 0 ? true : false;
        });
    };

    var getFirstContact = function () {
        return dfd.async().then(function (contacts) {
            if (contacts.length > 0) {
                return contacts[0];
            }
        return contacts
    });
    };


    // Test:

// Show all contacts
dfd.async(dumpContacts)
.then(getContacts)
.then.apply(dfd.promise, [
  // Does contact 'Jane' exist?
  contactExists("Jane").then(function (exists) {
    log("Contact 'Jane' exist: " + exists);
  })
  , getFirstContact().then(function (firstContact) {
    log("first contact: " + firstContact);
  })
]);
&#13;
function log() {
  var args = Array.prototype.slice.call(arguments).join(", ");
  console.log(args);
  var output = document.getElementById('output');
  output.innerHTML += args + "<br/>";
  return output
};

var dfd = {
  "active": false,
  "promise": void 0,
  "process": function process(args) {
    return args === "" || !!args
           ? args instanceof Function && args.call(this) || args 
           : this.active = true && this.promise
  },
  "err": function err(e) {
    console.log("rejected `Promise` reason:", e || void 0);
  },
  "fn": function fn(args) {
    var _dfd = this;
    _dfd.promise = !_dfd.active
                   ? _dfd.process(args)
                   : _dfd.promise.then(function(deferred) {
                       return deferred
                     })
                     .catch(_dfd.err);
    return Promise.resolve(_dfd.promise).then(function(data) {
        _dfd.active = false;
        return data
      })
      .catch(_dfd.err)
  }
};

var dumpContacts = function() {
  log('Calling back-end to get contact list.');
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      log('New data received from back-end.');
      resolve(["Mary", "Frank", "Klaus"]);
    }, 3000);
  });
};

var getContacts = function() {
  return dfd.fn().then(function(contacts) {
    for (var i = 0; i < contacts.length; i++) {
      log("Contact " + (i + 1) + ": " + contacts[i]);
    }
  });
};

var contactExists = function(contactName) {
  return dfd.fn().then(function(contacts) {
    return contacts.indexOf(contactName) >= 0 ? true : false;
  });
};

var getFirstContact = function() {
  return dfd.fn().then(function(contacts) {
    if (contacts.length > 0) {
      return contacts[0];
    }
    return contacts
  });
};


// Test:

// Show all contacts
dfd.fn(dumpContacts)
  .then(getContacts)
  .then(function() {
    // Does contact 'Jane' exist?
    return contactExists("Jane").then(function(exists) {
      log("Contact 'Jane' exist: " + exists);
    })
  })
  .then(function() {
    return getFirstContact().then(function(firstContact) {
      log("first contact: " + firstContact);
    })
  });
&#13;
&#13;
&#13;