JavaScript, Node.js : Array.forEach가 비동기식입니까?


답변:


392

아니요, 차단 중입니다. 알고리즘사양을 살펴보십시오 .

그러나 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 에서 가져온 것입니다 .

또 다른 옵션은 웹 워커 일 수 있습니다 .


37
당신은 Node.js를를 사용하는 경우, 또한 사용을 고려 process.nextTick 대신의 setTimeout의를
마르첼로 Bastea - 포르테에게

28
기술적으로, CPU는 절대 잠들지 않기 때문에 forEach는 "차단"되지 않습니다. 노드 앱이 이벤트에 응답 할 것으로 예상 할 때 "차단"처럼 느껴질 수있는 동기 및 CPU 바운드입니다.
Dave Dopson

3
async 는 아마도 더 적절한 해결책 일 것입니다 (사실 누군가 누군가가 그것을 답변으로 게시 한 것을 보았습니다!).
제임스

6
나는이 대답을 믿었지만 어떤 경우에는 잘못된 것 같습니다. 예를 들어 문장을 차단 forEach하지 않으며 루프를 await사용해야합니다 for. stackoverflow.com/questions/37962880/…
Richard

3
@Richard : 물론입니다. await내부 async기능 만 사용할 수 있습니다 . 그러나 forEach비동기 함수가 무엇인지 모릅니다. 비동기 함수는 약속을 반환하는 함수일뿐입니다. forEach콜백에서 반환 된 약속을 처리 하시겠습니까 ? forEach콜백의 반환 값을 완전히 무시합니다. 비동기 콜백 인 경우 비동기 콜백 만 처리 할 수 ​​있습니다.
Felix Kling

80

비동기 친화적 인 버전 Array.forEach과 비슷한 버전이 필요하면 Node.js 'async'모듈에서 사용할 수 있습니다 : http://github.com/caolan/async ...이 모듈은 브라우저에서도 작동합니다 .

async.each(openFiles, saveFile, function(err){
    // if any of the saves produced an error, err would equal that error
});

2
비동기 순서가 한 번에 하나의 항목에 대해서만 (컬렉션 순서대로 ) 실행되도록 해야하는 경우 eachSeries대신 사용해야 합니다.
matpop

@JohnKennedy 전에 본 적이 있습니다!
Xsmael

16

노드에서 실제로 무거운 계산을 수행하는 데 공통적 인 패턴이 있습니다.

노드는 단일 스레드입니다 (고의적 인 디자인 선택 으로 Node.js 란 무엇입니까? 참조 ). 이는 단일 코어 만 사용할 수 있음을 의미합니다. 최신 상자에는 8 개, 16 개 또는 더 많은 코어가 있으므로 시스템의 90 % 이상이 유휴 상태가 될 수 있습니다. REST 서비스의 공통 패턴은 코어 당 하나의 노드 프로세스를 시작하여 http://nginx.org/ 와 같은 로컬로드 밸런서 뒤에 배치하는 것 입니다.

아이를 포크 -당신이하려고하는 일에 대해, 무거운 리프팅을하기 위해 아이 프로세스를 포크하는 또 다른 일반적인 패턴이 있습니다. 단점은 자식 프로세스가 백그라운드에서 많은 계산을 수행 할 수 있고 부모 프로세스는 다른 이벤트에 응답 할 수 있다는 것입니다. 중요한 것은이 하위 프로세스와 메모리를 공유 할 수 없거나 공유해서는 안된다는 것입니다 (많은 왜곡과 일부 고유 코드가없는 경우는 아님). 메시지를 전달해야합니다. 입력 및 출력 데이터의 크기가 수행해야하는 계산에 비해 작 으면 아름답게 작동합니다. 하위 node.js 프로세스를 시작하고 이전에 사용한 것과 동일한 코드를 사용할 수도 있습니다.

예를 들면 다음과 같습니다.

var child_process = require ( 'child_process');
run_in_child 함수 (배열, cb) {
    var process = child_process.exec ( 'node libfn.js', function (err, stdout, stderr) {
        var output = JSON.parse (stdout);
        cb (err, 출력);
    });
    process.stdin.write (JSON.stringify (array), 'utf8');
    process.stdin.end ();
}

11
분명히하기 위해 ... 노드는 단일 스레드가 아니지만 JavaScript 실행은 가능합니다. IO와 별도의 스레드에서 실행되지 않는 것.
Brad

3
@ 브래드-아마도. 구현에 따라 다릅니다. 적절한 커널 지원을 통해 노드와 커널 간의 인터페이스는 이벤트 기반-kqueue (mac), epoll (linux), IO 완료 포트 (windows) 일 수 있습니다. 폴백으로 스레드 풀도 작동합니다. 당신의 기본 요점은 맞습니다. 저수준 노드 구현에는 여러 스레드가있을 수 있습니다. 그러나 전체 언어 모델을 손상시킬 수 있으므로 JS 사용자 영역에 직접 노출시키지 않습니다.
Dave Dopson

4
맞습니다. 개념이 많은 것을 혼동했기 때문에 명확하게 설명하고 있습니다.
브래드

6

Array.forEach는 대기하지 않는 컴퓨팅 작업을위한 것이며 이벤트 루프에서 계산을 비동기식으로 수행 할 수있는 것은 없습니다 (멀티 코어 계산이 필요한 경우 웹 워커가 멀티 프로세싱을 추가 함). 여러 작업이 끝날 때까지 기다리려면 카운터를 사용하십시오. 카운터는 세마포어 클래스로 랩핑 할 수 있습니다.


5

2018-10-11 편집 : 아래 설명 된 표준을 통과하지 못할 가능성이 높은 것처럼 보입니다. 파이프 라인 을 대안으로 고려하십시오 (정확하게 동일하게 작동하지 않지만 비슷한 매너로 메소드를 구현할 수 있음).

이것이 바로 es7에 대해 흥분되는 이유입니다. 미래에 아래 코드와 같은 작업을 수행 할 수 있습니다 (일부 사양은 완전하지 않으므로주의해서 사용하십시오.이 최신 정보를 유지하려고 노력할 것입니다). 그러나 기본적으로 new :: bind 연산자를 사용하면 객체의 프로토 타입에 메소드가 포함 된 것처럼 객체에서 메소드를 실행할 수 있습니다. 예 : [Object] :: [Method] 일반적으로 [Object]를 호출합니다. [ObjectsMethod]

오늘 (24-July-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;
    });
};

1

타사 라이브러리가 필요없는 짧은 비동기 함수입니다.

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

이것은 어떻게 비동기식입니까? AFAIK # 호출이 즉시 실행됩니까?
Giles Williams

1
물론 즉시, 그러나 모든 반복이 언제 완료되는지 알 수있는 콜백 함수가 있습니다. 여기서 "반복자"인수는 콜백이있는 노드 스타일 비동기 함수입니다. async.each와 비슷합니다
Rax Wunter

3
이것이 어떻게 비동기인지 알 수 없습니다. 호출 또는 적용은 동 기적입니다. 콜백을 갖는 비동기를하지 않습니다
adrianvlupu

자바 스크립트에서 사람들이 비동기라고 말하면 코드 실행이 주 이벤트 루프를 차단하지 않는다는 것을 의미합니다 (일명, 한 줄의 코드에서 프로세스가 멈추지 않습니다). 콜백을 넣는 것만으로 코드가 비 동기화되지는 않으며 setTimeout 또는 setInterval과 같은 일종의 이벤트 루프 해제를 사용해야합니다. 기다리는 시간이 길어지면 다른 코드가 중단없이 실행될 수 있습니다.
vasilevich

0

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의 또 다른 변형


0

예를 들어 다음과 같은 솔루션을 코딩하는 것이 가능합니다.

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

반면에 "for"보다 훨씬 느립니다.

그렇지 않으면 우수한 비동기 라이브러리가 다음을 수행 할 수 있습니다 : https://caolan.github.io/async/docs.html#each


0

다음은 테스트하기 위해 실행할 수있는 작은 예입니다.

[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

이것은 async.foreach 또는 다른 병렬 메소드를 작성하더라도 발생합니다. for 루프는 IO 프로세스가 아니기 때문에 Nodejs는 항상 동기식으로 처리합니다.
Sudhanshu Gaur

-2

블루 버드 라이브러리의 Promise.each 를 사용하십시오 .

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

주어진 된 배열 또는 약속을 포함하는 배열 (또는 약속과 값의 조합)의 약속 동안이 방법을 반복 반복자 서명과 함수 (값 인덱스, 길이) (A)의 분해 값 입력 배열에서 각각의 약속. 반복적으로 발생합니다. 반복자 함수가 promise 또는 thenable을 반환하면 다음 반복을 계속하기 전에 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");
});
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.