program story

Node.js에서 여러 콜백을 기다리는 관용적 방법

inputbox 2020. 8. 22. 08:47
반응형

Node.js에서 여러 콜백을 기다리는 관용적 방법


임시 파일에 의존하는 몇 가지 작업을 수행해야한다고 가정합니다. 여기서 Node에 대해 이야기하고 있으므로 이러한 작업은 분명히 비동기 적입니다. 임시 파일을 삭제할 수있는시기를 알기 위해 모든 작업이 완료 될 때까지 기다리는 관용적 방법은 무엇입니까?

다음은 내가 원하는 작업을 보여주는 코드입니다.

do_something(tmp_file_name, function(err) {});
do_something_other(tmp_file_name, function(err) {});
fs.unlink(tmp_file_name);

그러나 이렇게 작성하면 처음 두 사람이 파일을 사용할 기회를 얻기 전에 세 번째 호출을 실행할 수 있습니다. 호출을 중첩하지 않고 계속 진행하기 전에 처음 두 호출이 이미 완료되었는지 (콜백을 호출했는지) 보장 할 수있는 방법이 필요합니다 (실제로는 동기로 만들지 않음).

콜백에 이벤트 이미 터를 사용하고 카운터를 수신자로 등록하는 것에 대해 생각했습니다. 카운터는 완료된 이벤트를 수신하고 보류중인 작업 수를 계산합니다. 마지막 작업이 끝나면 파일이 삭제됩니다. 그러나 경쟁 조건의 위험이 있으며 이것이 일반적으로 이러한 일이 수행되는 방식인지 확실하지 않습니다.

Node 사람들은 이런 종류의 문제를 어떻게 해결합니까?


최신 정보:

이제 다음을 살펴 보는 것이 좋습니다.

  • 약속

    Promise 개체는 지연 및 비동기 계산에 사용됩니다. Promise는 아직 완료되지 않았지만 향후에 예상되는 작업을 나타냅니다.

    인기있는 Promise 라이브러리는 bluebird 입니다. A는 약속 이유를 살펴 보라고 조언 할 것 입니다.

    이를 설정하려면 promise를 사용해야합니다.

    fs.readFile("file.json", function (err, val) {
        if (err) {
            console.error("unable to read file");
        }
        else {
            try {
                val = JSON.parse(val);
                console.log(val.success);
            }
            catch (e) {
                console.error("invalid json in file");
            }
        }
    });
    

    이것으로 :

    fs.readFileAsync("file.json").then(JSON.parse).then(function (val) {
        console.log(val.success);
    })
    .catch(SyntaxError, function (e) {
        console.error("invalid json in file");
    })
    .catch(function (e) {
        console.error("unable to read file");
    });
    
  • 생성기 : 예를 들어 co .

    Promise를 사용하여 nodejs와 브라우저를위한 제너레이터 기반 제어 흐름의 장점으로, 비 차단 코드를 멋진 방식으로 작성할 수 있습니다.

    var co = require('co');
    
    co(function *(){
      // yield any promise
      var result = yield Promise.resolve(true);
    }).catch(onerror);
    
    co(function *(){
      // resolve multiple promises in parallel
      var a = Promise.resolve(1);
      var b = Promise.resolve(2);
      var c = Promise.resolve(3);
      var res = yield [a, b, c];
      console.log(res);
      // => [1, 2, 3]
    }).catch(onerror);
    
    // errors can be try/catched
    co(function *(){
      try {
        yield Promise.reject(new Error('boom'));
      } catch (err) {
        console.error(err.message); // "boom"
     }
    }).catch(onerror);
    
    function onerror(err) {
      // log any uncaught errors
      // co will not throw any errors you do not handle!!!
      // HANDLE ALL YOUR ERRORS!!!
      console.error(err.stack);
    }
    

내가 올바르게 이해한다면 아주 좋은 비동기 라이브러리를 보아야한다고 생각합니다 . 특히 시리즈를 봐야합니다 . github 페이지에서 발췌 한 사본 :

async.series([
    function(callback){
        // do some stuff ...
        callback(null, 'one');
    },
    function(callback){
        // do some more stuff ...
        callback(null, 'two');
    },
],
// optional callback
function(err, results){
    // results is now equal to ['one', 'two']
});


// an example using an object instead of an array
async.series({
    one: function(callback){
        setTimeout(function(){
            callback(null, 1);
        }, 200);
    },
    two: function(callback){
        setTimeout(function(){
            callback(null, 2);
        }, 100);
    },
},
function(err, results) {
    // results is now equals to: {one: 1, two: 2}
});

또한이 라이브러리는 브라우저에서도 실행할 수 있습니다.


가장 간단한 방법은 비동기 작업을 시작할 때 정수 카운터를 증가시킨 다음 콜백에서 카운터를 감소시키는 것입니다. 복잡성에 따라 콜백은 카운터에서 0을 확인한 다음 파일을 삭제할 수 있습니다.

조금 더 복잡한 것은 객체 목록을 유지하는 것이며, 각 객체는 상태 코드뿐만 아니라 작업을 식별하는 데 필요한 속성 (함수 호출 일 수도 있음)을 갖습니다. 콜백은 상태 코드를 완료로 설정합니다.

그런 다음 대기 (사용 process.nextTick)하고 모든 작업이 완료되었는지 확인 하는 루프가 있습니다 . 카운터에 비해이 방법의 장점은 모든 미해결 작업이 완료 될 수있는 경우 모든 작업이 실행되기 전에 카운터 기술로 인해 파일이 조기에 삭제된다는 것입니다.


// simple countdown latch
function CDL(countdown, completion) {
    this.signal = function() { 
        if(--countdown < 1) completion(); 
    };
}

// usage
var latch = new CDL(10, function() {
    console.log("latch.signal() was called 10 times.");
});

"네이티브"솔루션은 없지만 노드 용 흐름 제어 라이브러리백만 개 입니다. Step :

Step(
  function(){
      do_something(tmp_file_name, this.parallel());
      do_something_else(tmp_file_name, this.parallel());
  },
  function(err) {
    if (err) throw err;
    fs.unlink(tmp_file_name);
  }
)

또는 Michael이 제안했듯이 카운터는 더 간단한 솔루션이 될 수 있습니다. 세마포어 모형을보십시오 . 다음과 같이 사용합니다.

do_something1(file, queue('myqueue'));
do_something2(file, queue('myqueue'));

queue.done('myqueue', function(){
  fs.unlink(file);
});

Node : 이벤트의 핵심에서 프로그래밍 패러다임의 속도와 효율성을 활용하는 또 다른 솔루션을 제공하고 싶습니다.

Promise 또는 흐름 제어를 관리하도록 설계된 모듈 async로 수행 할 수있는 모든 작업은 이벤트와 간단한 상태 머신을 사용하여 수행 할 수 있으며, 다른 옵션보다 이해하기 쉬운 방법론을 제공한다고 생각합니다.

예를 들어 여러 파일의 길이를 병렬로 합산한다고 가정합니다.

const EventEmitter = require('events').EventEmitter;

// simple event-driven state machine
const sm = new EventEmitter();

// running state
let context={
  tasks:    0,    // number of total tasks
  active:   0,    // number of active tasks
  results:  []    // task results
};

const next = (result) => { // must be called when each task chain completes

  if(result) { // preserve result of task chain
    context.results.push(result);
  }

  // decrement the number of running tasks
  context.active -= 1; 

  // when all tasks complete, trigger done state
  if(!context.active) { 
    sm.emit('done');
  }
};

// operational states
// start state - initializes context
sm.on('start', (paths) => {
  const len=paths.length;

  console.log(`start: beginning processing of ${len} paths`);

  context.tasks = len;              // total number of tasks
  context.active = len;             // number of active tasks

  sm.emit('forEachPath', paths);    // go to next state
});

// start processing of each path
sm.on('forEachPath', (paths)=>{

  console.log(`forEachPath: starting ${paths.length} process chains`);

  paths.forEach((path) => sm.emit('readPath', path));
});

// read contents from path
sm.on('readPath', (path) => {

  console.log(`  readPath: ${path}`);

  fs.readFile(path,(err,buf) => {
    if(err) {
      sm.emit('error',err);
      return;
    }
    sm.emit('processContent', buf.toString(), path);
  });

});

// compute length of path contents
sm.on('processContent', (str, path) => {

  console.log(`  processContent: ${path}`);

  next(str.length);
});

// when processing is complete
sm.on('done', () => { 
  const total = context.results.reduce((sum,n) => sum + n);
  console.log(`The total of ${context.tasks} files is ${total}`);
});

// error state
sm.on('error', (err) => { throw err; });

// ======================================================
// start processing - ok, let's go
// ======================================================
sm.emit('start', ['file1','file2','file3','file4']);

다음을 출력합니다.

시작 : 4 개 경로 처리 시작
forEachPath : 4 개의 프로세스 체인 시작
  readPath : file1
  readPath : file2
  processContent : file1
  readPath : file3
  processContent : file2
  processContent: file3
  readPath: file4
  processContent: file4
The total of 4 files is 4021

Note that the ordering of the process chain tasks is dependent upon system load.

You can envision the program flow as:

start -> forEachPath -+-> readPath1 -> processContent1 -+-> done
                      +-> readFile2 -> processContent2 -+
                      +-> readFile3 -> processContent3 -+
                      +-> readFile4 -> processContent4 -+

For reuse, it would be trivial to create a module to support the various flow-control patterns, i.e. series, parallel, batch, while, until, etc.


The simplest solution is to run the do_something* and unlink in sequence as follows:

do_something(tmp_file_name, function(err) {
    do_something_other(tmp_file_name, function(err) {
        fs.unlink(tmp_file_name);
    });
});

Unless, for performance reasons, you want to execute do_something() and do_something_other() in parallel, I suggest to keep it simple and go this way.


Wait.for https://github.com/luciotato/waitfor

using Wait.for:

var wait=require('wait.for');

...in a fiber...

wait.for(do_something,tmp_file_name);
wait.for(do_something_other,tmp_file_name);
fs.unlink(tmp_file_name);

With pure Promises it could be a bit more messy, but if you use Deferred Promises then it's not so bad:

Install:

npm install --save @bitbar/deferred-promise

Modify your code:

const DeferredPromise = require('@bitbar/deferred-promise');

const promises = [
  new DeferredPromise(),
  new DeferredPromise()
];

do_something(tmp_file_name, (err) => {
  if (err) {
    promises[0].reject(err);
  } else {
    promises[0].resolve();
  }
});

do_something_other(tmp_file_name, (err) => {
  if (err) {
    promises[1].reject(err);
  } else {
    promises[1].resolve();
  }
});

Promise.all(promises).then( () => {
  fs.unlink(tmp_file_name);
});

참고URL : https://stackoverflow.com/questions/5172244/idiomatic-way-to-wait-for-multiple-callbacks-in-node-js

반응형