Node.js의 비동기 반복 패턴

node.js에서 IO를 수행 할 때처럼 비동기 적으로 프로그래밍 할 때 특히 이해하기 어려운 패턴이 있습니다.

예를 들어 다음 루틴을 프로그래밍해야한다고 가정 해 보겠습니다.

데이터베이스에 개체 컬렉션을 삽입 한 다음 완료되면 콜백을 호출합니다. 그래서 이것을 동기식으로 작성해야한다면 다음과 같이 할 수 있습니다 :

function insertCollection(collection) {
  for(var i = 0; i < collection.length; i++) {
    db.insert(collection[i]);
  }
}

따라서 node.js를 사용하기 때문에 db.insert가 비동기 일 가능성이 높습니다. 이것을 비동기 함수로 바꾸어야합니다.

나는이 같은 명백하게 잘못된 implents를 보았다 :

function insertCollection(collection, callback) {
  for(var i = 0; i < collection.length; i++) {
    db.insert(collection[i], function(err) {
      if (err) {
        throw err;
      }
    });
  }
  callback();
}

이 문제는 명백합니다. 콜백은 백그라운드에서 모든 db.inserts를 시작한 직후에 호출되며 완료 할 기회를 남기지 않습니다. 콜백이 호출되면 삽입이 종료되지 않습니다.

또 다른 접근법은 다음과 같습니다.

function insertCollection(collection, callback) {
  for(var i = 0; i < collection.length; i++) {
    (function(i) {
      db.insert(collection[i], function(err) {
        if (err) {
          callback(err);
          return;
        }
        if (i == (collection.length - 1)) {
          callback();
        }
      });
    })(i);
  }
}

따라서 "마지막 삽입물이 콜백 할 때 전화해야합니다"라고 생각할 유혹이 있습니다. 그러나 이것은 틀린 것입니다. 마지막 한 콜백이 실행될 때 첫 번째 삽입이 여전히 실행 중일 수 있습니다. 너는 결코 알지 못한다.

가장 안전한 방법은 다음과 같이하는 것입니다.

function insertCollection(collection, callback) {
  var inserted = 0;
  for(var i = 0; i < collection.length; i++) {
    db.insert(collection[i], function(err) {
      if (err) {
        callback(err);
        return;
      }
      if (++inserted == collection.length) {
        callback();
      }
    });
  }
}

모든 삽입이 콜백되었을 때만 콜백해야합니다.

직렬화

때로는 흐름 및 / 또는 실행 순서를 제어하려고합니다.

이 경우 삽입물을 완벽하게 정렬하거나 오류가 발생하면 삽입을 중단하여보다 쉽게 ​​복구 할 수 있습니다.

이 경우 다음과 같이 할 수 있습니다.

function insertCollection(collection, callback) {
  var coll = collection.slice(0); // clone collection
  (function insertOne() {
    var record = coll.splice(0, 1)[0]; // get the first record of coll and reduce coll by one
    db.insert(record, function(err) {
      if (err) { callback(err); return }
      if (coll.length == 0) {
        callback();
      } else {
        insertOne();
      }
    }
  })();
}

여기에서는 꼬리 재귀를 사용하여 레코드를 계속 삽입합니다.

이 예제에는 한 가지 문제가 있습니다. 스택을 사용하므로 컬렉션이 너무 크면 스택이 끊어 질 수 있습니다.

이 문제에 대한 한 가지 해결책은 재귀 할 때 스택을 포기하는 것입니다. 그리고 당신은 0의 timeout 값을 가진 setTimeout을 사용하여 그것을 할 수 있습니다. 스택이 unwind 된 후에 inner 함수가 호출되도록합니다 :

function insertCollection(collection, callback) {
  var coll = collection.slice(0); // clone collection
  (function insertOne() {
    var record = coll.splice(0, 1)[0]; // get the first record of coll and reduce coll by one
    db.insert(record, function(err) {
      if (err) { callback(err); return }
      if (coll.length == 0) {
        callback();
      } else {
        setTimeout(insertOne, 0);
      }
    }
  })();
}

후속 조치

후속 기사 Node.js의 비동기 반복 패턴보기 - 2 부

최신 정보:

또한 (Tim Caswell이 지적한 바와 같이) 콜백으로 끝나는 대신 이벤트 루프로 예외가 되돌아 가지 않는 것이 중요합니다. 따라서 db.insert 또는 다른 외부 함수 호출을 래핑해야합니다. 마지막 예는 다음과 같습니다.

function insertCollection(collection, callback) {
  var coll = collection.slice(0); // clone collection
  (function insertOne() {
    var record = coll.splice(0, 1)[0]; // get the first record of coll and reduce coll by one
    try {
      db.insert(record, function(err) {
        if (err) { callback(err); return }
        if (coll.length == 0) {
          callback();
        } else {
          insertOne();
        }
      }
    } catch (exception) {
      callback(exception);
    }
  })();
}


블로그 이미지

칩사마코더

,