javascript promise deferred

javascript - Promesas para promesas que aún no se han creado sin utilizar el patrón diferido



promise deferred (3)

Problema 1: solo se permite una solicitud de API en un momento dado, por lo que las solicitudes de red reales se ponen en cola mientras hay una que aún no se ha completado. Una aplicación puede llamar al nivel API en cualquier momento y esperar una promesa a cambio. Cuando la llamada API se pone en cola, la promesa de la solicitud de red se crearía en algún momento en el futuro: ¿qué devolver a la aplicación? Así es como se puede resolver con una promesa "proxy" diferida:

var queue = []; function callAPI (params) { if (API_available) { API_available = false; return doRealNetRequest(params).then(function(data){ API_available = true; continueRequests(); return data; }); } else { var deferred = Promise.defer(); function makeRequest() { API_available = false; doRealNetRequest(params).then(function(data) { deferred.resolve(data); API_available = true; continueRequests(); }, deferred.reject); } queue.push(makeRequest); return deferred.promise; } } function continueRequests() { if (queue.length) { var makeRequest = queue.shift(); makeRequest(); } }

Problema 2: algunas llamadas de API se eliminan para que los datos que se envíen se acumulen con el tiempo y luego se envíen en un lote cuando se alcanza el tiempo de espera. La aplicación que llama a la API espera una promesa a cambio.

var queue = null; var timeout = 0; function callAPI2(data) { if (!queue) { queue = {data: [], deferred: Promise.defer()}; } queue.data.push(data); clearTimeout(timeout); timeout = setTimeout(processData, 10); return queue.deferred.promise; } function processData() { callAPI(queue.data).then(queue.deferred.resolve, queue.deferred.reject); queue = null; }

Dado que diferido se considera un antipatrón (consulte también ¿ Cuándo alguien necesitaría crear un diferido? ), La pregunta es: ¿es posible lograr las mismas cosas sin un diferido (o hacks equivalentes como la new Promise(function (resolve, reject) {outerVar = [resolve, reject]}); ), utilizando la API estándar de Promise?


Cuando la llamada API se pone en cola, la promesa de la solicitud de red se crearía en algún momento en el futuro: ¿qué devolver a la aplicación?

Su primer problema puede resolverse con un encadenamiento prometedor. No desea ejecutar una solicitud determinada hasta que todas las solicitudes anteriores hayan finalizado y desea ejecutarlas en serie en orden. Este es exactamente el patrón de diseño para el encadenamiento prometedor. Puedes resolverlo así:

var callAPI = (function() { var p = Promise.resolve(); return function(params) { // construct a promise that chains to prior callAPI promises var returnP = p.then(function() { return doRealNetRequest(params); }); // make sure the promise we are chaining to does not abort chaining on errors p = returnP.then(null, function(err) { // handle rejection locally for purposes of continuing chaining return; }); // return the new promise return returnP; } })();

En esta solución, se crea una nueva promesa de inmediato con .then() para que pueda devolver esa promesa de inmediato; no hay necesidad de crear una promesa en el futuro. La llamada real a doRealNetRequest() se encadena a esta promesa .then() devuelta devolviendo su valor en el controlador .then() . Esto funciona porque, la devolución de llamada que proporcionamos a .then() no se llama hasta algún momento en el futuro cuando las promesas anteriores en la cadena se hayan resuelto, lo que nos da un activador automático para ejecutar el siguiente en la cadena cuando finalice la anterior. .

Esta implementación supone que desea que continúen las llamadas de API en cola incluso después de que se devuelva un error. Las pocas líneas de código adicionales alrededor del comentario de handle rejection del handle rejection están ahí para garantizar que la cadena continúe incluso donde una promesa anterior rechaza. Cualquier rechazo se devuelve a la persona que llama como se esperaba.

Aquí hay una solución para su segundo (lo que usted llama rebote).

la pregunta es: ¿es posible lograr las mismas cosas sin un diferido (o hacks equivalentes como la nueva Promesa (función (resolver, rechazar) {outsideVar = [resolver, rechazar]});), usando la API Promise estándar?

Hasta donde yo sé, el tipo de problema de rebote requiere un poco de pirateo para exponer la capacidad de desencadenar las devoluciones de llamadas de resolución / rechazo desde fuera del ejecutor de la promesa. Se puede hacer un poco más limpio de lo que propone al exponer una sola función que está dentro de la función del ejecutor de la promesa en lugar de exponer directamente los manejadores de resolución y rechazo.

Esta solución crea un cierre para almacenar el estado privado que se puede usar para administrar cosas de una llamada a callAPI2() a la siguiente.

Para permitir que el código en un momento indeterminado en el futuro desencadene la resolución final, esto crea una función local dentro de la función del ejecutor de la promesa (que tiene acceso a las funciones de resolve y reject ) y luego la comparte en el ámbito superior (pero aún privado) por lo tanto, se puede callAPI2 desde fuera de la función del ejecutor de la promesa, pero no desde fuera de callAPI2 .

var callAPI2 = (function() { var p, timer, trigger, queue = []; return function(data) { if (!p) { p = new Promise(function(resolve) { // share completion function to a higher scope trigger = function() { resolve(queue); // reinitialize for future calls p = null; queue = []; } }).then(callAPI); } // save data and reset timer queue.push(data); clearTimeout(timer); setTimeout(trigger, 10); return p; } })();


Promesas por promesas que aún no se han creado

... son fáciles de construir al encadenar una invocación con la devolución de llamada que crea la promesa de una promesa que representa la disponibilidad para crearla en el futuro.

Si está haciendo una promesa para una promesa, nunca debe usar el patrón diferido. Debería usar diferidos o el constructor Promise si y solo si hay algo asíncrono que desea esperar, y que aún no involucra promesas . En todos los demás casos, debe componer múltiples promesas.

Cuando tu dices

Cuando la llamada API se pone en cola, la promesa de la solicitud de red se crearía en algún momento en el futuro

entonces no debe crear un diferido que luego pueda resolver con la promesa una vez que se haya creado (o peor, resolverlo con los resultados de las promesas una vez que se cumpla la promesa), sino que debe obtener una promesa para el momento en el futuro se realizará la solicitud de red. Básicamente vas a escribir

return waitForEndOfQueue().then(makeNetworkRequest);

y, por supuesto, vamos a necesitar mutar la cola respectivamente.

var queue_ready = Promise.resolve(true); function callAPI(params) { var result = queue_ready.then(function(API_available) { return doRealNetRequest(params); }); queue_ready = result.then(function() { return true; }); return result; }

Esto tiene el beneficio adicional que necesitará para tratar explícitamente los errores en la cola. Aquí, cada llamada devuelve una promesa rechazada una vez que falla una solicitud (probablemente querrá cambiar eso): en su código original, la queue se atascó (y probablemente no lo notó).

El segundo caso es un poco más complicado, ya que involucra una llamada setTimeout . Esta es una primitiva asincrónica para la que necesitamos construir una promesa manualmente, pero solo por el tiempo de espera, y nada más. Nuevamente, vamos a obtener una promesa para el tiempo de espera, y luego simplemente encadenar nuestra llamada API a eso para obtener la promesa de que queremos regresar.

function TimeoutQueue(timeout) { var data = [], timer = 0; this.promise = new Promise(resolve => { this.renew = () => { clearTimeout(timer); timer = setTimeout(resolve, timeout); }; }).then(() => { this.constructor(timeout); // re-initialise return data; }); this.add = (datum) => { data.push(datum); this.renew(); return this.promise; }; } var queue = new TimeoutQueue(10); function callAPI2(data) { return queue.add(data).then(callAPI); }

Puede ver aquí a) cómo la lógica callAPI2 está completamente factorizada a partir de callAPI2 (lo que podría no haber sido necesario pero hace un buen punto) yb) cómo el constructor de la promesa solo se preocupa por el tiempo de espera y nada más. Ni siquiera necesita "filtrar" la función de resolve como lo haría un diferido, lo único que pone a disposición del exterior es esa función de renew que permite extender el temporizador.


Puede crear una cola, que resuelve promesas en el orden colocado en la cola

window.onload = function() { (function(window) { window.dfd = {}; that = window.dfd; that.queue = queue; function queue(message, speed, callback, done) { if (!this.hasOwnProperty("_queue")) { this._queue = []; this.done = []; this.res = []; this.complete = false; this.count = -1; }; q = this._queue, msgs = this.res; var arr = Array.prototype.concat.apply([], arguments); q.push(arr); msgs.push(message); var fn = function(m, s, cb, d) { var j = this; if (cb) { j.callback = cb; } if (d) { j.done.push([d, j._queue.length]) } // alternatively `Promise.resolve(j)`, `j` : `dfd` object // `Promise` constructor not necessary here, // included to demonstrate asynchronous processing or // returned results return new Promise(function(resolve, reject) { // do stuff setTimeout(function() { div.innerHTML += m + "<br>"; resolve(j) }, s || 0) }) // call `cb` here, interrupting queue .then(cb ? j.callback.bind(j, j._queue.length) : j) .then(function(el) { console.log("queue.length:", q.length, "complete:", el.complete); if (q.length > 1) { q.splice(0, 1); fn.apply(el, q[0]); return el } else { el._queue = []; console.log("queue.length:", el._queue.length , "complete:", (el.complete = !el._queue.length)); always(promise(el), ["complete", msgs]) }; return el }); return j } , promise = function(t) { ++t.count; var len = t._queue.length, pending = len + " pending"; return Promise.resolve( len === 1 ? fn.apply(t, t._queue[0]) && pending : !(t.complete = len === 0) ? pending : t ) } , always = function(elem, args) { if (args[0] === "start") { console.log(elem, args[0]); } else { elem.then(function(_completeQueue) { console.log(_completeQueue, args); // call any `done` callbacks passed as parameter to `.queue()` Promise.all(_completeQueue.done.map(function(d) { return d[0].call(_completeQueue, d[1]) })) .then(function() { console.log(JSON.stringify(_completeQueue.res, null, 2)) }) }) } }; always(promise(this), ["start", message, q.length]); return window }; }(window)); window .dfd.queue("chain", 1000) .dfd.queue("a", 1000) .dfd.queue("b", 2000) .dfd.queue("c", 2000, function callback(n) { console.log("callback at queue index ", n, this); return this }, function done(n) { console.log("all done callback attached at queue index " + n) }) .dfd.queue("do", 2000) .dfd.queue("other", 2000) .dfd.queue("stuff", 2000); for (var i = 0; i < 10; i++) { window.dfd.queue(i, 1000) }; window.dfd.queue.apply(window.dfd, ["test 1", 5000]); window.dfd.queue(["test 2", 1000]); var div = document.getElementsByTagName("div")[0]; var input = document.querySelector("input"); var button = document.querySelector("button"); button.onclick = function() { window.dfd.queue(input.value, 0); input.value = ""; } }

<input type="text" /> <button>add message</button> <br> <div></div>