异步执行多个任务并返回JavaScript函数中的第一个成功结果 [英] Execute multiple tasks asynchronously and return first successful result in JavaScript function

查看:109
本文介绍了异步执行多个任务并返回JavaScript函数中的第一个成功结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



在这个函数中,我有多种方式来检索数据,即


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

每个选项可能需要其自己的时间才能完成,并且可能成功或失败。

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

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



我还有一个问题。



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

示例伪代码:

 函数getOrder(id){

var order;

//如果在缓存中发现订单,则提前返回。
if(order = cache.get(id))return order;

//继续从后端REST API获取订单。
order = cache.put(backend.get(id));

退货订单;

请教如何在JavaScript中实现这些需求。

迄今为止发现的解决方案:




  1. 最快结果



    JavaScript ES6解决方案



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





Promise.race(可迭代)



返回一个承诺,可以在迭代器中的第一个承诺解决时解决。




  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解决方案



Ref: http://gpars.org/1.1.0/guide/guide/single.html

 导入groovyx.gpars.dataflow.Promise 
导入groovyx.gpars.dataflow.Select
import groovyx.gpars.group.DefaultPGroup
import java.util.concurrent.atomic.AtomicBoolean
$ b / **
*演示如何使用数据流任务和选择同时运行计算的最快结果。
*它显示一个waz在知道结果后取消较慢的任务
* /

final group = new DefaultPGroup()
final done = new AtomicBoolean )

group.with {
Promise p1 =任务{
sleep(1000)
if(done.get())return
10 * 10 + 1

Promise p2 =任务{
sleep(1000)
if(done.get())return
5 * 20 + 2
如果(done.get())返回
1 * 100 + 3
}

final alt = new Select(group,p1,p2,p3,Select.createTimeout(500))
def result = alt.select()
done.set(true)
println结果:+ result
}




  1. 早期回报和互动功能

    角度承诺与ES6生成器结合???



      angular.module('org.common')
    .service( SpaceService',函数($ q,$ timeout,Restangular,$ angularCacheFactory){


    var _spacesCache = $ angularCacheFactory('spacesCache',{
    maxAge:120000,//项目在两分钟后过期
    deleteOnExpire:'aggressive',
    onExpire:function(key,value){
    Restangular.one('organizations',key).getList('spaces')。然后(函数(数据){
    _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('立即尝试缓存...'); //进度通知
    if(spaces = _spacesCache.get(orgId)){
    $ timeout.cancel(timerID);
    deferred.resolve(spaces);
    }
    return deferred.promise;
    },
    getAllSpaces2:function(orgId){
    //设置一个伪消除器
    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);
    //对数据做
    返回'';
    },
    editSpace:function(space){
    _spacesCache.remove(space.organization.id);
    //对数据做
    返回'';
    },
    deleteSpace:function(space){
    console.table(space);
    _spacesCache.remove(space.organization.id);
    return space.remove();
    }
    };
    });



解决方案

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

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


  • 只要任何承诺失败,返回的承诺就会被拒绝



但是您想要反转这样的逻辑如下:




  • 只要任何承诺成功,那么返回的承诺就会被解决

  • 如果所有承诺都失败,则返回的承诺将被拒绝。



这应该可以通过 invert ()将成功转换为失败并将转换为 的实用程序。

 函数invert(promise){
return promise.then(function(x){
return $ q.defer()。reject(x).promise;
},function(x){
返回$ qd 。EFER()解析(x)的.promise;
});

$ / code>

first()

 函数first(arr){
return invert($ q。所有(arr.map(反转)));
}

注意:


  • 输入 arr 是一组承诺

  • 原生实现我们假设array.map()(否则你可以显式地循环以获得相同的效果)
  • 外部 invert() first()恢复它返回的承诺的正确含义
  • 我没有特别的角度经验,所以我可能犯了句法错误 - 但是我认为逻辑是正确的。



然后 getOrder()



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

$ / code>

因此, getOrder(id)应该返回一个订单的承诺(不是直接订单)。


I have to write a javaScript function that return some data to the caller.

In that function I have multiple ways to retrieve data i.e.,

  1. Lookup from cache
  2. Retrieve from HTML5 LocalStorage
  3. Retrieve from REST Backend (bonus: put the fresh data back into cache)

Each option may take its own time to finish and it may succeed or fail.

What I want to do is, to execute all those three options asynchronously/parallely and return the result whoever return first.

I understand that parallel execution is not possible in JavaScript since it is single threaded, but I want to at least execute them asynchronously and cancel the other tasks if one of them return successfully result.

I have one more question.

Early return and continue executing the remaining task in a JavaScript function.

Example pseudo code:

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

Please advice how to implement those requirements in JavaScript.

Solutions discovered so far:

  1. Fastest Result

    JavaScript ES6 solution

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

Promise.race(iterable)

Returns a promise that resolves when the first promise in the iterable resolves.

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 solution

Ref: 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. Early Return and Interactive Function

    Angular Promises combined with ES6 generators???

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

解决方案

Personally, I would try the three asynchronous retrievals sequentially, starting with the least expensive and ending with the most expensive. However, responding to the first of three parallel retrievals is an interesting problem.

You should be able to exploit the characteristic of $q.all(promises), by which :

  • as soon as any of the promises fails then the returned promise is rejected
  • if all promises are successful then the returned promise is resolved.

But you want to invert the logic such that :

  • as soon as any of the promises is successful then the returned promise is resolved
  • if all promises fail then the returned promise is rejected.

This should be achievable with an invert() utility which converts success to failure and vice versa.

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

And a first() utility, to give the desired behaviour :

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

Notes:

  • the input arr is an array of promises
  • a native implementation of array.map() is assumed (otherwise you can explicitly loop to achieve the same effect)
  • the outer invert() in first() restores the correct sense of the promise it returns
  • I'm not particularly experienced in angular, so I may have made syntactic errors - however I think the logic is correct.

Then getOrder() will be something like this :

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

Thus, getOrder(id) should return a Promise of an order (not the order directly).

这篇关于异步执行多个任务并返回JavaScript函数中的第一个成功结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆