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

时间:2014-01-20 20:20:18

标签: java javascript angularjs groovy promise

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

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

  1. 从缓存中查找
  2. 从HTML5 LocalStorage检索
  3. 从REST后端检索(奖励:将新数据放回缓存中)
  4. 每个选项可能需要自己的时间才能完成,可能会成功或失败。

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

    我理解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

    2.   

      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. 早期返回和互动功能

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

3 个答案:

答案 0 :(得分:7)

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

您应该能够利用$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是一系列承诺
  • 假定array.map()的本机实现(否则您可以显式循环以实现相同的效果)
  • invert()中的first()外部恢复了对其返回的承诺的正确感觉
  • 我在角度方面没有特别的经验,所以我可能会出现语法错误 - 但我认为逻辑是正确的。

然后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)应该返回订单的承诺(而不是直接订单)。

答案 1 :(得分:0)

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

因此,在您的服务中有一个函数可以对您的其他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.
}

答案 2 :(得分:0)

示例getOrder中的问题在于,如果3个查找函数将是异步的,则不会立即从它们那里获取订单,因为它们没有阻塞,{{1将返回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 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);    
}