Ejecute múltiples tareas de forma asíncrona y devuelva el primer resultado exitoso en la función JavaScript

Tengo que escribir una función javaScript que devuelva algunos datos a la persona que llama.

En esa función tengo múltiples formas de recuperar datos, es decir,

Búsqueda desde cachéRecuperar de HTML5 LocalStorageRecuperar de REST Backend (bono: volver a poner los datos nuevos en caché)

Cada opción puede tomar su propio tiempo para finalizar y puede tener éxito o fallar.

Lo que quiero hacer es ejecutar estas tres opciones de forma asincrónica / paralela y devolver el resultado a quien sea que regrese primero.

Entiendo que la ejecución paralela no es posible en JavaScript ya que es de un solo hilo, pero quiero al menos ejecutarlos de forma asíncrona y cancelar las otras tareas si una de ellas devuelve el resultado exitosamente.

Tengo una pregunta más.

Retorno temprano y continuar ejecutando la tarea restante en una función de JavaScript.

Ejemplo de pseudo código:

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

Por favor aconseje cómo implementar esos requisitos en JavaScript.

Soluciones descubiertas hasta el momento:El resultado más rápidoSolución JavaScript ES6

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

Promesa.race (iterable)

Devuelve una promesa que se resuelve cuando se resuelve la primera promesa en el iterable.

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"
});
Solución Java / Groovy

Árbitro: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
}

Retorno temprano y función interactiva

¿Promesas angulares combinadas con generadores 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();
    }
};
});

Respuestas a la pregunta(3)

Su respuesta a la pregunta