JavaScript, Node.js: Array.forEach асинхронный?

У меня есть вопрос относительно встроенной Array.forEach реализации JavaScript: ведется ли она асинхронно? Например, если я вызываю:

[many many elements].forEach(function () {lots of work to do})

Будет ли это неблокировать?

Ответ 1

Нет, это блокировка. Посмотрите на спецификацию алгоритма.

Однако, возможно, более понятная реализация дана в 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.

Другим вариантом могут быть веб-работники.

Ответ 2

Если вам нужна асинхронная дружественная версия 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
});

Ответ 3

Существует общий шаблон для выполнения действительно тяжелых вычислений в Node, который может быть применим к вам...

Node является однопоточным (как преднамеренный выбор дизайна, см. Что такое Node.js?); это означает, что он может использовать только одно ядро. В современных коробках есть 8, 16 или даже больше сердечников, поэтому это может привести к тому, что 90% машины не будут работать. Общая схема службы REST заключается в том, чтобы запустить один процесс Node на ядро ​​и поместить их за локальный балансировщик нагрузки, например 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(err, output);
    });
    process.stdin.write(JSON.stringify(array), 'utf8');
    process.stdin.end();
}

Ответ 4

Array.forEach предназначен для вычисления чего-то не ожидающего, и нет ничего, что можно было бы сделать, делая вычисления асинхронными в цикле событий (веб-пользователи добавляют многопроцессорность, если вам нужно многоядерное вычисление). Если вы хотите дождаться завершения нескольких задач, используйте счетчик, который вы можете обернуть в классе семафора.

Ответ 5

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

Ответ 6

Это короткая асинхронная функция для использования без использования сторонних библиотек

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

Ответ 7

Существует пакет на 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 all done 
  }).then(function () {
    console.log('All requests have finished');
});

Также другая вариация forAllAsync

Ответ 8

Можно запрограммировать даже такое решение, как это, например:

 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

Ответ 9

Вот небольшой пример, который вы можете запустить для его проверки:

[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

Ответ 10

Нет, forEach не является асинхронным по умолчанию, как уже говорили другие.

Кроме того, нет необходимости в библиотеках, вы можете использовать любой из ваших массивов с очень простой функцией, подобной этой:

const iterate = async(values, func) => {
  for(let elem of values) {
    await func(elem);
  }
};

Ответ 11

Это не асинхронный. Это блокировка. Те, кто сначала изучил язык, такой как Java, C или Python, прежде чем попробовать JS, запутаются, когда попытаются добавить произвольную задержку или вызов API в теле цикла.

Скажем, вы попробуете это:

const array = [1, 2, 3, 4, 5];

array.forEach((el, i) => {
    setTimeout(() => {
        console.log(el);
    }, 1000);
});

Код выполняется по порядку, и следующая итерация цикла не начинает выполняться, пока предыдущая не пройдет свою последнюю строку.

Вы, вероятно, ожидаете, что setTimeout задержит выполнение, пока не истечет время вашей задержки. Это не так, потому что метод setTimeout фактически возвращает значение немедленно. В Node.js он возвращает объект Timeout, а в браузере возвращает число, указывающее на экземпляр Timeout.

Когда что-то возвращается, следующая строка начинает выполняться. Таким образом, вы получите такой эффект, когда программа выполняется чуть более 1 секунды, и перед закрытием она выплевывает 5 консольных журналов почти одновременно.

Если вы хотите, чтобы время ожидания заставило цикл ждать, пока не пройдет его задержка, вы можете использовать следующий код.

function forEachWithCallback(callback) {
    const arrayCopy = this;
    let index = 0;
    const next = () => {
        index++;
        if (arrayCopy.length > 0) {
            callback(arrayCopy.shift(), index, next);
        }
    }
    next();
}

Array.prototype.forEachWithCallback = forEachWithCallback;

const array = [1, 2, 3, 4, 5];

array.forEachWithCallback((el, i, next) => {
    setTimeout(() => {
        console.log(el);
        next();
    }, 1000);
});

Вместо использования собственного метода forEach, который поставляется с массивами JS, вы можете добавить свой собственный с обратным вызовом. Использование этого заставит ваши итерации ждать окончания своего предыдущего таймаута. Следующая итерация не может начаться до тех пор, пока в вашем теле цикла не будет вызван "следующий" метод. Таким образом, выполнение этого кода займет чуть более 5 секунд, а один раз в секунду будет один файл console.log.

Это хорошо работает и с JS Promises, например, когда вы хотите использовать ajax.

// "request" implementation can be found in the link at the bottom of this answer

const array = [1, 2, 3, 4, 5];

array.forEachWithPromise((el, i, next) => {
    request({
        method: 'GET',
        hostname: 'httpbin.org',
        path: '/get?myArg=' + el
    }).then((res) => {
        const responseBody = JSON.parse(res.body);
        console.log(responseBody.args.myArg);
        next();
    }).error((err) => {
        console.error(err);
    });
});

Вот суть связки бесплатного кода, который вы можете использовать как в браузере, так и в Node.js для создания циклов for-each, без внешних пакетов или зависимостей. https://gist.github.com/ajb413/d55489eec64db0bb4079a8d7af733aab

Ответ 12

Используйте Promise.each из Bluebird библиотеки.

Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise

Этот метод выполняет итерацию по массиву или обещание массива, который содержит обещания (или сочетание обещаний и значений) с заданной функцией итератора с сигнатурой (значением, индексом, длиной), где значение является разрешенным значением соответствующее обещание во входном массиве. Итерация происходит последовательно. Если функция итератора возвращает обещание или его можно использовать, то результат обещания ожидается до продолжения следующей итерации. Если какое-либо обещание во входном массиве отклонено, то возвращенное обещание также отклоняется.

Если все итерации успешно разрешены, 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");
});