JavaScript неотъемлемо однопоточный, и «асинхронный» означает «где-то в конце очереди событий», на самом деле.
Из-за этого нет возможности подождать, пока задача async закончится: вы должны завершить работу для следующей задачи async для запуска. Единственный способ - добавить код, который будет вызываться в конце задачи async, называемой цепочкой async (или callback).
Однако, к счастью, существуют рамки, которые помогут вам построить этот материал более организованным образом. Одной из таких рамок является Promises. Я оставлю вас посетить эту ссылку для базового введения.
Во-первых, давайте «обещаем» требуемые вызовы API. Первый из них:
function historyLastWeek() {
return new Promise(function(resolve, reject) {
chrome.history.search({
'text': '',
'startTime': oneWeekAgo, // don't forget that part
'maxResults': 999999999
}, function(historyItems) {
if(chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
} else {
resolve(historyItems);
}
});
});
}
Приведенный выше код возвращает Promise, который будет выполняться вызов chrome.history.search
API и либо решимость с деталями истории, или отклонять с сообщением об ошибке.
Пункт обещания состоит в том, что вы можете использовать .then()
на нем, цепочки звонков.
Давайте также promisify chrome.history.getVisits
(примечание, это занимает пункт истории, так как мы хотим):
function getVisits(historyItem) {
return new Promise(function(resolve, reject) {
chrome.history.getVisits({url: historyItem.url}, function(visitItems) {
if(chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
} else {
resolve({
historyItem: historyItem, // let's keep track of it
visitItems: visitItems
});
}
});
});
}
Итак, у нас есть 2 обещания, которые возвращают массив результатов. Как до tie them together?
Во-первых, я предполагаю, что вы не нарушаете внутренний цикл (поэтому я запускаю «getVisits» в «параллельном»). Для этого у нас есть Promise.all
. Давайте посмотрим ..
historyLastWeek().then(function(historyItems) {
return Promise.all(
// map() returns the array of results of applying a function
// to all members of the array
historyItems.map(getVisits)
);
}).then(function(results) {
// results is an array of objects
results.each(function(result) {
// here, result.historyItem is the history item,
// and result.visitItems is an array of visit items
/* here goes your code #1 */
result.visitItems.each(function(visitItem) {
/* here goes your "Do something that depends on the first function" code */
});
/* here goes your code #2 */
});
/* here goes your code #3 */
}).catch(function(errorMsg) {
// oh noes
});
Если вам нужно сделать что-то после кода # 3, то вам необходимо promisify той последней функции, а также, и добавить еще один .then()
.
Этот код имеет неудачное свойство: поскольку JavaScript не лень, all()
будет собирать все результаты в одной монолитной 2-мерного массива перед любой из кода выполняется, и вы не можете прервать внутренний цикл рано.
Вы можете изменить это на execute sequentially, вместо того чтобы собирать массив, а затем обрабатывать его.
historyLastWeek().then(function(historyItems) {
return historyItems.reduce(function(sequence, historyItem) {
return sequence.then(function() {
return getVisits(historyItem);
).then(function(result) {
// here, result.historyItem is the history item,
// and result.visitItems is an array of visit items
/* here goes your code #1 */
result.visitItems.each(function(visitItem) {
/* here goes your "Do something that depends on the first function" code */
});
/* here goes your code #2 */
// Gotta return some promise
return Promise.resolve();
});
}, Promise.resolve());
}).then(function() {
/* here goes your code #3 */
}).catch(function(errorMsg) {
// oh noes
});
См. Ссылку выше для объяснения того, как все это работает.
Это спорно, если это проще и чище, чем не-Promise кода, но, по крайней мере, это альтернатива ручного построения цепочки обратных вызовов.
Хотя невозможно сделать магическую синхронизацию, вы можете сделать это с помощью [Обещания] (http://www.html5rocks.com/en/tutorials/es6/promises/). – Xan