У меня есть вопрос относительно нативной Array.forEach
реализации JavaScript: он ведет себя асинхронно? Например, если я позвоню:
[many many elements].forEach(function () {lots of work to do})
Это будет неблокирующим?
У меня есть вопрос относительно нативной Array.forEach
реализации JavaScript: он ведет себя асинхронно? Например, если я позвоню:
[many many elements].forEach(function () {lots of work to do})
Это будет неблокирующим?
Ответы:
Нет, это блокировка. Посмотрите на спецификацию алгоритма .
Однако, возможно, более понятная реализация дана в MDN :
if (!Array.prototype.forEach)
{
Array.prototype.forEach = function(fun /*, thisp */)
{
"use strict";
if (this === void 0 || this === null)
throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in t)
fun.call(thisp, t[i], i, t);
}
};
}
Если вам нужно выполнить много кода для каждого элемента, вы должны рассмотреть возможность использования другого подхода:
function processArray(items, process) {
var todo = items.concat();
setTimeout(function() {
process(todo.shift());
if(todo.length > 0) {
setTimeout(arguments.callee, 25);
}
}, 25);
}
а затем позвоните с помощью:
processArray([many many elements], function () {lots of work to do});
Это было бы неблокирующим тогда. Пример взят из высокопроизводительного JavaScript .
Другим вариантом могут быть веб-работники .
forEach
это не блок на await
заявления например , и вы скорее должны использовать for
цикл: stackoverflow.com/questions/37962880/...
await
внутри async
функции. Но forEach
не знает, что такое асинхронные функции. Имейте в виду, что асинхронные функции - это просто функции, возвращающие обещание. Ожидаете ли вы forEach
справиться с обещанием, возвращенным после обратного вызова? forEach
полностью игнорирует возвращаемое значение из обратного вызова. Он сможет обработать асинхронный обратный вызов только в том случае, если это будет сам асинхронный вызов.
Если вам нужна асинхронная версия Array.forEach
и аналогичные, они доступны в модуле «async» Node.js: http://github.com/caolan/async ... в качестве бонуса этот модуль также работает в браузере ,
async.each(openFiles, saveFile, function(err){
// if any of the saves produced an error, err would equal that error
});
eachSeries
вместо этого.
Существует общая схема выполнения действительно тяжелых вычислений в Node, которая может быть применима к вам ...
Node является однопоточным (в качестве преднамеренного выбора дизайна см. Что такое Node.js? ); это означает, что он может использовать только одно ядро. Современные боксы имеют 8, 16 или даже больше ядер, так что это может оставить 90 +% простоя машины. Обычный шаблон для службы REST - запуск одного процесса узла на ядро и размещение его за локальным балансировщиком нагрузки, таким как http://nginx.org/ .
Форкинг ребенка - для того, что вы пытаетесь сделать, есть еще одна распространенная модель, которая заключается в том, что вы выполняете тяжелую работу над дочерним процессом. Положительным моментом является то, что дочерний процесс может выполнять тяжелые вычисления в фоновом режиме, в то время как ваш родительский процесс реагирует на другие события. Загвоздка в том, что вы не можете / не должны делить память с этим дочерним процессом (не без МНОГО потрясений и некоторого нативного кода); Вы должны передавать сообщения. Это будет прекрасно работать, если размер ваших входных и выходных данных будет небольшим по сравнению с вычислением, которое необходимо выполнить. Вы даже можете запустить дочерний процесс node.js и использовать тот же код, который вы использовали ранее.
Например:
var child_process = require ('child_process'); function run_in_child (array, cb) { var process = child_process.exec ('node libfn.js', function (err, stdout, stderr) { var output = JSON.parse (stdout); cb (ошибка, вывод); }); process.stdin.write (JSON.stringify (array), 'utf8'); process.stdin.end (); }
Array.forEach
Предназначен для ожидающих вычислений, и ничего не получится сделать асинхронными вычисления в цикле событий (веб-работники добавляют многопроцессорность, если вам нужны многоядерные вычисления). Если вы хотите дождаться завершения нескольких задач, используйте счетчик, который можно обернуть в класс семафора.
Изменить 2018-10-11: Похоже, есть большая вероятность, что описанный ниже стандарт может не пройти, рассмотрите конвейеризацию как альтернативу (не ведет себя точно так же, но методы могут быть реализованы в подобной усадьбе).
Именно поэтому я в восторге от es7, в будущем вы сможете сделать что-то вроде приведенного ниже кода (некоторые спецификации не завершены, поэтому используйте с осторожностью, я постараюсь держать это в курсе). Но в основном используя оператор new :: bind, вы сможете запустить метод для объекта, как если бы прототип объекта содержал метод. например, [Object] :: [Method], где обычно вы вызываете [Object]. [ObjectsMethod]
Обратите внимание, чтобы сделать это сегодня (24 июля-16) и заставить его работать во всех браузерах, вам понадобится перенести ваш код для следующих функций: импорт / экспорт , функции стрелок , обещания , асинхронное / ожидание и, что наиболее важно, связывание функций . Приведенный ниже код может быть модифицирован для использования только функции связывания, если это необходимо, и все эти функциональные возможности сегодня доступны благодаря использованию babel .
YourCode.js (где « много работы » нужно просто вернуть обещание, разрешив его после выполнения асинхронной работы.)
import { asyncForEach } from './ArrayExtensions.js';
await [many many elements]::asyncForEach(() => lots of work to do);
ArrayExtensions.js
export function asyncForEach(callback)
{
return Promise.resolve(this).then(async (ar) =>
{
for(let i=0;i<ar.length;i++)
{
await callback.call(ar, ar[i], i, ar);
}
});
};
export function asyncMap(callback)
{
return Promise.resolve(this).then(async (ar) =>
{
const out = [];
for(let i=0;i<ar.length;i++)
{
out[i] = await callback.call(ar, ar[i], i, ar);
}
return out;
});
};
Это короткая асинхронная функция для использования без сторонних библиотек
Array.prototype.each = function (iterator, callback) {
var iterate = function () {
pointer++;
if (pointer >= this.length) {
callback();
return;
}
iterator.call(iterator, this[pointer], iterate, pointer);
}.bind(this),
pointer = -1;
iterate(this);
};
На npm есть пакет для легкой асинхронности для каждого цикла .
var forEachAsync = require('futures').forEachAsync;
// waits for one request to finish before beginning the next
forEachAsync(['dogs', 'cats', 'octocats'], function (next, element, index, array) {
getPics(element, next);
// then after all of the elements have been handled
// the final callback fires to let you know it's all done
}).then(function () {
console.log('All requests have finished');
});
Также еще один вариант для AllAsync
Например, можно закодировать даже такое решение:
var loop = function(i, data, callback) {
if (i < data.length) {
//TODO("SELECT * FROM stackoverflowUsers;", function(res) {
//data[i].meta = res;
console.log(i, data[i].title);
return loop(i+1, data, errors, callback);
//});
} else {
return callback(data);
}
};
loop(0, [{"title": "hello"}, {"title": "world"}], function(data) {
console.log("DONE\n"+data);
});
С другой стороны, это намного медленнее, чем «для».
В противном случае превосходная библиотека Async может сделать это: https://caolan.github.io/async/docs.html#each
Вот небольшой пример, который вы можете запустить, чтобы проверить это:
[1,2,3,4,5,6,7,8,9].forEach(function(n){
var sum = 0;
console.log('Start for:' + n);
for (var i = 0; i < ( 10 - n) * 100000000; i++)
sum++;
console.log('Ended for:' + n, sum);
});
Он будет производить что-то вроде этого (если это займет слишком меньше / много времени, увеличьте / уменьшите количество итераций):
(index):48 Start for:1
(index):52 Ended for:1 900000000
(index):48 Start for:2
(index):52 Ended for:2 800000000
(index):48 Start for:3
(index):52 Ended for:3 700000000
(index):48 Start for:4
(index):52 Ended for:4 600000000
(index):48 Start for:5
(index):52 Ended for:5 500000000
(index):48 Start for:6
(index):52 Ended for:6 400000000
(index):48 Start for:7
(index):52 Ended for:7 300000000
(index):48 Start for:8
(index):52 Ended for:8 200000000
(index):48 Start for:9
(index):52 Ended for:9 100000000
(index):45 [Violation] 'load' handler took 7285ms
Использование Promise.each из Bluebird библиотеки.
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
Этот метод выполняет итерацию по массиву или обещанию массива, которое содержит обещания (или сочетание обещаний и значений) с заданной функцией итератора с сигнатурой (значение, индекс, длина), где значение является разрешенным значением соответствующее обещание во входном массиве. Итерация происходит поочередно. Если функция итератора возвращает обещание или toableable, то результат обещания ожидается перед продолжением следующей итерации. Если какое-либо обещание во входном массиве отклонено, то возвращенное обещание также отклоняется.
Если все итерации успешно разрешены, Promise.each преобразуется в исходный массив без изменений . Однако, если одна итерация отклоняет или выдает ошибку, Promise.each немедленно прекращает выполнение и не обрабатывает дальнейшие итерации. В этом случае возвращается исходное или ошибочное значение, а не исходный массив.
Этот метод предназначен для побочных эффектов.
var fileNames = ["1.txt", "2.txt", "3.txt"];
Promise.each(fileNames, function(fileName) {
return fs.readFileAsync(fileName).then(function(val){
// do stuff with 'val' here.
});
}).then(function() {
console.log("done");
});