JavaScript, Node.js: является ли Array.forEach асинхронным? - PullRequest
338 голосов
/ 19 февраля 2011

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

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

Это будет неблокирующим?

Ответы [ 11 ]

360 голосов
/ 19 февраля 2011

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

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

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

73 голосов
/ 27 февраля 2012

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

Существует общая схема выполнения действительно тяжелых вычислений в 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(err, output);
    });
    process.stdin.write(JSON.stringify(array), 'utf8');
    process.stdin.end();
}
4 голосов
/ 24 июля 2016

Редактировать 2018-10-11: Похоже, есть большая вероятность, что описанный ниже стандарт может не пройти, рассмотрим конвейеризацию в качестве альтернативы (не ведет себя точно так же, но методы могут быть реализованыв подобной усадьбе).

Именно поэтому я взволнован по поводу es7, в будущем вы сможете сделать что-то вроде приведенного ниже кода (некоторые спецификации не завершены, поэтому используйте с осторожностью, я будупостарайтесь держать это в курсе).Но в основном используя оператор new :: bind, вы сможете запустить метод для объекта, как если бы прототип объекта содержал метод.например, [Object] :: [Method], где обычно вы вызываете [Object]. [ObjectsMethod]

Обратите внимание, чтобы сделать это сегодня (24-июля-16) и заставить его работать во всех браузерах, которые вам понадобятсяпередайте ваш код для следующих функций: Импорт / Экспорт , Функции стрелок , Обещания , Асинхронное / Ожидание и, самое главное, функция связывания .Приведенный ниже код может быть изменен, чтобы использовать только функцию связывания, если это необходимо, все эти функциональные возможности сегодня доступны с помощью babel .

YourCode.js (где ' много работы дляdo 'должен просто вернуть обещание, разрешив его после выполнения асинхронной работы.)

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;
    });
};
4 голосов
/ 25 февраля 2011

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

1 голос
/ 22 июня 2014

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

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);
};
0 голосов
/ 30 июня 2019

Это не асинхронный.Это блокировка.Те, кто сначала выучил язык, такой как 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

0 голосов
/ 06 сентября 2018

Использование Promise.each из Синяя птица Библиотека.

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

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

Если все итерации разрешаются успешно, 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");
});
0 голосов
/ 17 января 2018

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

[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
0 голосов
/ 27 августа 2017

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

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

С другой стороны, оно намного медленнее, чем «для».

В противном случае, превосходноеАсинхронная библиотека может сделать это: https://caolan.github.io/async/docs.html#each

...