问题1:在给定时间只允许一个API请求,因此真实的网络请求排队,而尚未完成的请求。应用程序可以随时调用API级别并期望获得回报。当API调用排队时,将在未来的某个时刻创建网络请求的承诺 - 返回应用程序的内容是什么?这就是如何用延迟的“代理”承诺解决它:
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();
}
}
问题2:某些API调用被去抖动,以便随时间累积要发送的数据,然后在达到超时时批量发送。调用API的应用程序期待作为回报的承诺。
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;
}
由于延迟被认为是一种反模式,(另见When would someone need to create a deferred?),问题是 - 是否有可能在没有延迟(或类似new Promise(function (resolve, reject) {outerVar = [resolve, reject]});
的等效黑客)的情况下实现相同的事情,使用标准的Promise API?
答案 0 :(得分:4)
承诺尚未创造的承诺
...通过使用回调链接then
调用很容易构建,该回调创建了对promise的承诺,表示将来创建它的可用性。
如果您承诺承诺,则永远不会使用延迟模式。当且仅当存在您想要等待的异步时,您应该使用延迟或Promise
构造函数,并且它还没有涉及promises 。在所有其他情况下,您应该撰写多个承诺。
当你说
时当API调用排队时,将在未来的某个时间点创建网络请求的承诺
那么你不应该创建一个延迟,你可以在以后用承诺解决它(或者更糟糕的是,一旦承诺结算后用承诺结果解决它),但你应该得到一个承诺。将来的网络需求。基本上你要写
return waitForEndOfQueue().then(makeNetworkRequest);
当然我们需要分别改变队列。
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;
}
这具有额外的好处,您需要显式处理队列中的错误。在这里,每当一个请求失败时,每个调用都会返回一个被拒绝的承诺(您可能想要更改它) - 在您的原始代码中,queue
刚被卡住(您可能没有注意到)。
第二种情况有点复杂,因为它涉及setTimeout
调用。这是一个异步原语,我们需要手动构建一个承诺 - 但仅限于超时,而不是其他任何东西。同样,我们将获得超时的承诺,然后简单地将我们的API调用链接到那个以获得我们想要返回的承诺。
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);
}
你可以在这里看到a)如何从callAPI2
中完全分解去抖动逻辑(这可能不是必要的但是提出了一个很好的观点)和b)promise构造函数如何只关注超时和没有其他的。它甚至不需要像延迟那样“泄漏”resolve
函数,它唯一可用于外部的是renew
函数,它允许扩展计时器。
答案 1 :(得分:3)
当API调用排队时,网络请求的承诺就会出现 是在未来的某个时刻创建的 - 返回应用程序的内容是什么?
你的第一个问题可以通过承诺链来解决。在所有先前的请求完成之前,您不想执行给定的请求,并且您希望按顺序执行它们。这正是承诺链的设计模式。你可以解决这个问题:
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;
}
})();
在此解决方案中,实际上会立即使用.then()
创建新承诺,以便您可以立即返回该承诺 - 将来无需创建承诺。通过在doRealNetRequest()
处理程序中返回其值,对.then()
的实际调用将链接到此反复的.then()
承诺。这是有效的,因为我们提供给.then()
的回调直到链中的先前承诺解决后才会被调用,这给我们一个自动触发器来执行前一个链中的下一个饰面。
此实现假定您希望排队的API调用即使在返回错误后也能继续。围绕handle rejection
注释的额外几行代码可以确保即使先前的承诺拒绝,链也会继续。任何拒绝都会按预期返回给调用者。
这是你的第二个解决方案(你称之为去抖动)。
问题是 - 是否有可能在没有a的情况下实现相同的目标 推迟(或类似新的承诺的黑客(功能(解决, 拒绝){outerVar = [resolve,reject]});),使用标准的Promise API?
据我所知,debouncer类型的问题需要一点点黑客来揭示从promise执行者之外以某种方式触发解析/拒绝回调的能力。通过公开promise执行函数中的单个函数,而不是直接公开resolve和reject处理程序,可以比你提议的更干净一些。
此解决方案创建一个闭包来存储私有状态,可用于管理从一个callAPI2()
到下一个的调用。
为了在将来的不确定时间允许代码触发最终解析,这将在promise执行函数(可以访问resolve
和reject
函数)中创建一个本地函数,然后将其分配给更高(但仍然是私有)的范围,以便可以从promise执行函数外部调用,但不能从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;
}
})();
答案 2 :(得分:0)
您可以创建一个队列,该队列按队列
中的顺序解析承诺
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>