异步执行多个任务并在JavaScript函数中返回第一个成功的结果

我必须编写一个javaScript函数,将一些数据返回给调用者。

在该函数中,我有多种方法来检索数据,即

  1. 从缓存中查找
  2. 从HTML5 LocalStorage中检索
  3. 从REST后端检索(奖励:将新数据放回缓存中)

每个选项可能需要自己的时间来完成,它可能会成功或失败。

我想要做的是,异步/并行地执行所有这三个选项,并返回先返回的结果。

我理解JavaScript中不可能并行执行,因为它是单线程的,但我想至少异步执行它们并取消其他任务,如果其中一个返回成功结果。

我还有一个问题。

提前返回并继续执行JavaScript函数中的剩余任务。

伪代码示例:

function getOrder(id) { var order; // early return if the order is found in cache. if (order = cache.get(id)) return order; // continue to get the order from the backend REST API. order = cache.put(backend.get(id)); return order; } 

请建议如何在JavaScript中实现这些要求。

到目前为止发现的解

  1. 最快的结果

    JavaScript ES6解决方案

    参考: https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Promise.race(迭代器)

返回在iterable中的第一个promise解析时解析的promise。

 var p1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, "one"); }); var p2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, "two"); }); Promise.race([p1, p2]).then(function(value) { // value == "two" }); 

Java / Groovy解决方案

参考: http : //gpars.org/1.1.0/guide/guide/single.html

 import groovyx.gpars.dataflow.Promise import groovyx.gpars.dataflow.Select import groovyx.gpars.group.DefaultPGroup import java.util.concurrent.atomic.AtomicBoolean /** * Demonstrates the use of dataflow tasks and selects to pick the fastest result of concurrently run calculations. * It shows a waz to cancel the slower tasks once a result is known */ final group = new DefaultPGroup() final done = new AtomicBoolean() group.with { Promise p1 = task { sleep(1000) if (done.get()) return 10 * 10 + 1 } Promise p2 = task { sleep(1000) if (done.get()) return 5 * 20 + 2 } Promise p3 = task { sleep(1000) if (done.get()) return 1 * 100 + 3 } final alt = new Select(group, p1, p2, p3, Select.createTimeout(500)) def result = alt.select() done.set(true) println "Result: " + result } 
  1. 早期回归与互动function

    Angular Promises与ES6发生器相结合???

     angular.module('org.common') .service('SpaceService', function ($q, $timeout, Restangular, $angularCacheFactory) { var _spacesCache = $angularCacheFactory('spacesCache', { maxAge: 120000, // items expire after two min deleteOnExpire: 'aggressive', onExpire: function (key, value) { Restangular.one('organizations', key).getList('spaces').then(function (data) { _spacesCache.put(key, data); }); } }); /** * @class SpaceService */ return { getAllSpaces: function (orgId) { var deferred = $q.defer(); var spaces; if (spaces = _spacesCache.get(orgId)) { deferred.resolve(spaces); } else { Restangular.one('organizations', orgId).getList('spaces').then(function (data) { _spacesCache.put(orgId, data); deferred.resolve(data); } , function errorCallback(err) { deferred.reject(err); }); } return deferred.promise; }, getAllSpaces1: function (orgId) { var deferred = $q.defer(); var spaces; var timerID = $timeout( Restangular.one('organizations', orgId).getList('spaces').then(function (data) { _spacesCache.put(orgId, data); deferred.resolve(data); }), function errorCallback(err) { deferred.reject(err); }, 0); deferred.notify('Trying the cache now...'); //progress notification if (spaces = _spacesCache.get(orgId)) { $timeout.cancel(timerID); deferred.resolve(spaces); } return deferred.promise; }, getAllSpaces2: function (orgId) { // set up a dummy canceler var canceler = $q.defer(); var deferred = $q.defer(); var spaces; $timeout( Restangular.one('organizations', orgId).withHttpConfig({timeout: canceler.promise}).getList('spaces').then(function (data) { _spacesCache.put(orgId, data); deferred.resolve(data); }), function errorCallback(err) { deferred.reject(err); }, 0); if (spaces = _spacesCache.get(orgId)) { canceler.resolve(); deferred.resolve(spaces); } return deferred.promise; }, addSpace: function (orgId, space) { _spacesCache.remove(orgId); // do something with the data return ''; }, editSpace: function (space) { _spacesCache.remove(space.organization.id); // do something with the data return ''; }, deleteSpace: function (space) { console.table(space); _spacesCache.remove(space.organization.id); return space.remove(); } }; }); 

就个人而言,我会按顺序尝试三个异步检索,从最便宜的开始到最昂贵的结束。 但是,响应三个并行检索中的第一个是一个有趣的问题。

您应该能够利用$q.all(promises) ,其中:

  • 只要任何承诺失败,那么退回的承诺就会被拒绝
  • 如果所有承诺都成功,则返回的承诺得到解决。

但是你想要颠倒逻辑:

  • 只要任何承诺成功,那么退回的承诺就会得到解决
  • 如果所有承诺都失败,那么退回的承诺将被拒绝。

这应该可以通过invert()实用程序实现,该实用程序将成功转换为失败, 反之亦然

 function invert(promise) { return promise.then(function(x) { return $q.defer().reject(x).promise; }, function(x) { return $q.defer().resolve(x).promise; }); } 

first()实用程序,以提供所需的行为:

 function first(arr) { return invert($q.all(arr.map(invert))); } 

笔记:

  • 输入arr是一个promises数组
  • 假设是array.map()的本机实现(否则您可以显式循环以实现相同的效果)
  • first()的outer invert()恢复了它返回的promise的正确意义
  • 我在角度上并不是特别有经验,所以我可能会出现语法错误 – 但我认为逻辑是正确的。

然后getOrder()将是这样的:

 function getOrder(id) { return first([ cache.get(id), localStorage.get(id).then(cache.put), backend.get(id).then(cache.put).then(localStorage.put) ]); } 

因此, getOrder(id)应该返回订单的Promise(而不是订单的直接)。

示例getOrder的问题在于,如果3个查找函数将是异步的,则不会立即从它们返回订单,因为它们没有阻塞, getOrder将返回null ; 你最好定义一个回调函数,它对第一个返回的order数据采取行动,然后忽略其余的数据。

 var doSomethingWithTheOrder = function CallBackOnce (yourResult) { if (!CallBackOnce.returned) { CallBackOnce.returned = true; // Handle the returned data console.log('handle', yourResult); } else { // Ignore the rest console.log('you are too late'); } } 

使您的数据查找function接受回调

 function cacheLookUp(id, callback) { // Make a real lookup here setTimeout(function () { callback('order data from cache'); }, 3000); } function localeStorageLookUp(id, callback) { // Make a real lookup here setTimeout(function () { callback('order data from locale storage'); }, 1500); } function restLookUp(id, callback) { // Make a real lookup here setTimeout(function () { callback('order data from rest'); }, 5000); } 

并将回调函数传递给它们中的每一个

 function getOrder(id) { cacheLookUp(id, doSomethingWithTheOrder); localeStorageLookUp(id, doSomethingWithTheOrder); restLookUp(id, doSomethingWithTheOrder); } 

在api调用中创建一个广播事件,然后创建$ scope。$ on来监听那些广播,当$ on get激活时,执行刷新这些对象的function。

所以在你的服务中有一个函数可以调用你的rest api的ajax。 你将有3个ajax调用。 和3个听众。 他们每个人都会看起来像这样。

这只是sudo代码,但这种格式是你如何做到的

  $http({ method: "GET", url: url_of_api, }).success(function(data, *args, *kwargs){ $rooteScope.$braodcast('success', data) }) 

在你的控制器中有一个像这样的监听器

 $scope.$on('success', function(event, args){ // Check the state of the other objects, have they been refreshed - you probably want to set flags to check if (No Flags are Set): $scope.data = args // which would be the returned data adn $scope.data would be what you're trying to refresh. }