2

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

当一个已经在进行中并且由同一网页上的不同代码段启动时,避免调用 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

4

3 回答 3

2

如果希望减少对后端的不必要调用的数量,则坚持承诺,当它仍未解决时,将其返回以进行新的调用,而不是向后端发出另一个调用。

这是一个将异步函数(返回承诺)转换为仅在承诺仍未解决时才调用的例程。

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

于 2015-01-23T10:09:13.363 回答
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 的实际结果失败,否则它可以很好地完成工作。

您现在可以缓存任何承诺返回函数 - 上面的版本只缓存忽略参数 - 但您可以构造一个类似的具有 Map 和基于不同参数的缓存的函数 - 但让我们专注于您的用例。

var getContactsCached = cache(getContacts);

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

cache 方法实际上甚至与 Promise 无关——它所做的只是获取一个函数并缓存它的结果——你可以将它用于任何事情。事实上,如果你正在使用像 underscore 这样的库_.memoize,你已经可以使用它来为你做这件事了。

于 2015-04-28T19:55:49.363 回答
-4

编辑,更新

删除了“嵌套”ternary模式;添加

  • a) dfd.err(),.catch()处理Promise.reject(/* reason ? */) arguments传递给dfd.fn();
  • b)args === ""dfd.process()处理范围内"":空String传递argumentdfd.fn()
  • c) 替代“链接”.then()要求then.apply(dfd.promise, [contactExists, getFirstContact])

本机Error()传递为argumentdfd.fn(new Error("error"))global范围内处理;dfd.fn()仍然返回dfd.promise。可能会在 或 之前调整dfd.process(),以“早”返回Error或传递Errordfd.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)
  }
};

    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);
  })
]);

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);
    })
  });
<body>
  Must use browser that supportes the Promises API, such as Chrome

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

  <hr>
</body>

于 2015-01-24T08:10:48.883 回答