Node.js에서 비동기 함수의 긴 중첩을 피하는 방법


158

DB의 일부 데이터를 표시하는 페이지를 만들고 싶기 때문에 DB에서 해당 데이터를 가져 오는 함수를 만들었습니다. 나는 Node.js의 초보자 일 뿐이므로 이해할 수있는 한 모든 페이지를 단일 페이지 (HTTP 응답)로 사용하려면 모두 중첩해야합니다.

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

이와 같은 함수가 많으면 중첩이 문제가 됩니다.

이것을 피할 수있는 방법이 있습니까? 여러 비동기 함수를 결합하는 방법과 관련이 있다고 생각합니다. 이것은 근본적인 것으로 보입니다.


12
따라서 10 개의 비동기 기능이 있으면 10 단계의 들여 쓰기가 있습니까?
Kay Pale

이 링크가 도움이 될 수 있습니다. stackoverflow.com/a/4631909/290340
Evan Plaice 2012 년

1
또 다른 문제 : 사이에 다른 기능을 삽입 getSomeDate하고 getSomeOtherDate읽을 자식 역사 어렵게 많은 줄 들여 쓰기를 변경의 끝 업 ( git blame이 후도 쓸모가), 수동으로이 일을 할 때 당신은 가능성이 버그를 만들
다니엘 알더

답변:


73

흥미로운 관찰. JavaScript에서는 일반적으로 인라인 익명 콜백 함수를 명명 된 함수 변수로 바꿀 수 있습니다.

다음과 같은:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

다음과 같이 다시 작성 될 수 있습니다.

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

그러나 다른 곳에서 콜백 논리를 재사용하려는 경우가 아니라면 예와 같이 인라인 익명 함수를 읽는 것이 훨씬 쉽습니다. 또한 모든 콜백의 이름을 찾지 않아도됩니다.

또한 아래 주석에 언급 된 @pst 에서 내부 함수 내에서 클로저 변수에 액세스하는 경우 위의 내용은 간단한 번역이 아닙니다. 이러한 경우 인라인 익명 함수를 사용하는 것이 훨씬 더 좋습니다.


26
그러나 중첩되지 않은 경우 변수에 대한 일부 폐쇄 의미론 이 손실 될 있으므로 직접 번역이 아닙니다. 위의 예에서 'res'에 대한 액세스 getMoreData가 손실됩니다.

2
귀하의 솔루션이 고장 났다고 생각합니다. someDataParser실제로 모든 데이터를 구문 분석합니다 getMoreData. 그런 의미에서 함수 이름이 잘못되어 실제로 중첩 문제를 제거하지 않았 음을 알 수 있습니다.
Konstantin Schubert

63

케이, 간단히이 모듈 중 하나를 사용하십시오.

이것은 이것을 돌릴 것입니다 :

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', 'bobvance@potato.egg', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

이것으로 :

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', 'bobvance@potato.egg', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);

9
flow-js, step 및 async를 간략히 살펴 보았지만 함수 실행 순서 만 처리하는 것 같습니다. 필자의 경우 모든 들여 쓰기마다 인라인 클로저 변수에 액세스 할 수 있습니다. 예를 들어 함수는 다음과 같이 작동합니다. HTTP req / res 가져 오기, 쿠키의 DB에서 사용자 ID 가져 오기, 이후 사용자 ID의 전자 메일 가져 오기, 이후 전자 메일의 추가 데이터 가져 오기, ..., 나중에 Y의 경우 X 가져 오기 ... 내가 틀리지 않으면이 프레임 워크는 비동기 함수가 올바른 순서로 실행되도록 보장하지만 모든 함수 본문에서 클로저 (?)에 의해 자연스럽게 제공된 변수를 얻을 수있는 방법은 없습니다. 감사합니다 :)
Kay Pale

9
이 라이브러리의 순위를 정하기 위해 Github에서 각 별의 "별"수를 확인했습니다. 비동기는 약 3000으로 가장 높고, 단계는 약 1000으로, 나머지는 상당히 적습니다. 물론, 그들은 모두 똑같은 일을하지는 않습니다 :-)
kgilpin

3
@KayPale 나는 async.waterfall을 사용하는 경향이 있으며 때로는 다음 단계에서 필요한 것을 통과하거나 async.METHOD 호출 전에 변수를 정의하여 다운 라인을 사용할 수 있도록 각 단계 / 단계마다 고유 한 기능을 갖습니다. 또한 async. * 호출에 METHODNAME.bind (...)를 사용합니다.이 또한 잘 작동합니다.
트래커

빠른 질문 : 모듈 목록에서 마지막 두 모듈이 동일합니까? 즉 "async.js"및 "async"
dari0h 10

18

대부분 Daniel Vassallo에 동의합니다. 복잡하고 깊이 중첩 된 함수를 별도의 명명 된 함수로 나눌 수 있다면 일반적으로 좋은 생각입니다. 단일 함수 내에서 수행하는 것이 합리적 인 경우에는 사용 가능한 많은 node.js 비동기 라이브러리 중 하나를 사용할 수 있습니다. 사람들은이 문제를 해결하기 위해 다양한 방법을 고안 했으므로 node.js 모듈 페이지를보고 여러분의 생각을보십시오.

나는 이것을 위해 async.js 라는 모듈을 작성했다 . 이를 사용하여 위 예제를 다음과 같이 업데이트 할 수 있습니다.

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

이 접근 방식의 한 가지 좋은 점은 'series'함수를 'parallel'로 변경하여 데이터를 병렬로 가져 오기 위해 코드를 빠르게 변경할 수 있다는 것입니다. 또한 async.js는 브라우저 내부에서도 작동하므로 까다로운 비동기 코드가 발생하는 경우 node.js에서와 동일한 방법을 사용할 수 있습니다.

도움이 되길 바랍니다.


안녕하세요 Caolan과 답변 주셔서 감사합니다! 필자의 경우 모든 들여 쓰기마다 인라인 클로저 변수에 액세스 할 수 있습니다. 예를 들어 함수는 다음과 같이 작동합니다. HTTP req / res 가져 오기, 쿠키의 DB에서 사용자 ID 가져 오기, 이후 사용자 ID의 전자 메일 가져 오기, 이후 전자 메일의 추가 데이터 가져 오기, ..., 나중에 Y의 경우 X 가져 오기 ... 내가 실수하지 않은 경우, 제안하는 코드는 비동기 함수가 올바른 순서로 실행되도록 보장하지만 모든 함수 본문에서 원래 코드의 클로저로 변수를 자연스럽게 제공 할 수있는 방법이 없습니다. 그 경우입니까?
Kay Pale

3
달성하려는 것은 건축 적으로 데이터 파이프 라인이라고합니다. 이러한 경우 비동기 폭포를 사용할 수 있습니다.
Rudolf Meijering 2018 년

18

중첩 된 함수 나 모듈이 아닌 배열에이 트릭을 사용할 수 있습니다.

눈에 훨씬 쉽게.

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

병렬 프로세스 또는 병렬 프로세스 체인에 대한 관용구를 확장 할 수 있습니다.

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();

15

나는 이 목적으로 async.js 를 많이 좋아 합니다.

폭포 명령으로 문제가 해결됩니다.

폭포 (작업, [콜백])

각 함수의 결과를 배열의 다음 함수로 전달하는 일련의 함수 배열을 실행합니다. 그러나 함수 중 하나라도 콜백에 오류를 전달하면 다음 함수가 실행되지 않고 오류와 함께 기본 콜백이 즉시 호출됩니다.

인수

tasks-실행할 함수 배열. 각 함수에는 완료시 호출해야하는 콜백 (err, result1, result2, ...)이 전달됩니다. 첫 번째 인수는 오류 (null 일 수 있음)이며 추가 인수는 다음 작업에 인수로 전달됩니다. callback (err, [results])-모든 기능이 완료되면 실행할 선택적 콜백입니다. 마지막 작업의 콜백 결과가 전달됩니다.

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

req, res 변수는 전체 async.waterfall 호출을 포함하는 function (req, res) {}과 동일한 범위 내에서 공유됩니다.

뿐만 아니라 비동기는 매우 깨끗합니다. 내가 의미하는 것은 다음과 같이 많은 경우를 변경한다는 것입니다.

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

먼저 :

function(o,cb){
    function2(o,cb);
}

그런 다음

function2(o,cb);

그런 다음

async.waterfall([function2,function3,function4],optionalcb)

또한 비동기를 위해 준비된 많은 미리 만들어진 함수를 util.js에서 매우 빠르게 호출 할 수 있습니다. 당신이하고 싶은 일을 연결하고 o, cb가 보편적으로 처리되도록하십시오. 이것은 전체 코딩 프로세스를 크게 가속화시킵니다.


11

필요한 것은 약간의 구문 설탕입니다. 이것을 확인하십시오 :

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

깔끔 하지 않습니까? html이 배열이 된 것을 알 수 있습니다. 문자열이 불변이기 때문에 더 큰 문자열을 버리는 것보다 배열에 출력을 버퍼링하는 것이 좋습니다. 다른 이유는와의 또 다른 멋진 구문 때문입니다 bind.

Queue예제에서 실제로는 예제 일 뿐이며 partial다음과 같이 구현 될 수 있습니다.

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};

1
Queue.execute ()는 비동기 호출의 결과를 기다리지 않고 단순히 부분을 차례로 실행합니다.
ngn

감사합니다. 답변을 업데이트했습니다. 테스트는 다음과 같습니다. jsbin.com/ebobo5/edit (옵션 last기능 포함)
gblazex

안녕 galambalazs 및 답변 주셔서 감사합니다! 필자의 경우 모든 들여 쓰기마다 인라인 클로저 변수에 액세스 할 수 있습니다. 예를 들어 함수는 다음과 같이 작동합니다. HTTP req / res 가져 오기, 쿠키에 대한 DB에서 사용자 ID 가져 오기, 이후 사용자 ID에 대한 전자 메일 가져 오기, 이후 전자 메일에 대한 추가 데이터 가져 오기, ..., 나중에 Y에 대한 X 가져 오기 ... 내가 실수하지 않은 경우, 제안하는 코드는 비동기 함수가 올바른 순서로 실행되도록 보장하지만 모든 함수 본문에서 원래 코드의 클로저로 변수를 자연스럽게 제공 할 수있는 방법이 없습니다. 그 경우입니까?
Kay Pale

1
글쎄, 당신은 모든 답변에서 폐쇄를 잃어 버립니다. 공유 데이터 의 전역 범위에서 개체를 만드는 것이 가능합니다 . 예를 들어 첫 번째 함수가 추가 obj.email되고 다음 함수가 obj.email이를 사용 하여 삭제하거나 할당합니다 null.
gblazex

7

내가 찾은 이래로 Async.js 를 사랑 합니다. async.series긴 중첩을 피하기 위해 사용할 수 있는 기능이 있습니다.

선적 서류 비치:-


시리즈 (작업, [콜백])

일련의 기능을 직렬로 실행하십시오. 각 기능은 이전 기능이 완료된 후에 실행됩니다. [...]

인수

tasks-실행할 함수 배열로, 각 함수에는 완료시 호출해야하는 콜백이 전달됩니다. callback(err, [results])-모든 기능이 완료되면 실행되는 선택적 콜백. 이 함수는 배열에 사용 된 콜백에 전달 된 모든 인수의 배열을 가져옵니다.


예제 코드에 적용하는 방법은 다음과 같습니다.

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});

6

내가 본 가장 간단한 구문 설탕은 노드 약속입니다.

npm install node-promise || 자식 클론 https://github.com/kriszyp/node-promise

이를 사용하여 비동기 메소드를 다음과 같이 연결할 수 있습니다.

firstMethod().then(secondMethod).then(thirdMethod);

각각의 반환 값은 다음에 인수로 제공됩니다.


3

당신이 한 일은 비동기 패턴을 취하여 순차적으로 호출되는 3 개의 함수에 적용합니다. 각각의 함수는 시작하기 전에 이전 함수가 완료되기를 기다립니다. 즉, 동기식으로 만들었습니다 . 비동기 프로그래밍의 요점은 여러 기능을 한 번에 실행하고 각 기능이 완료 될 때까지 기다릴 필요가 없다는 것입니다.

getSomeDate ()가 getMoreData ()에 아무것도 제공하지 않는 getSomeOtherDate ()에 아무것도 제공하지 않으면 js가 허용하는대로 비동기식으로 호출하지 않거나 js가 허용하는대로 비동기식으로 호출하지 않는 이유는 무엇입니까? 단일 기능?

플로우를 제어하기 위해 중첩을 사용할 필요가 없습니다. 예를 들어, 3 개가 모두 완료된시기를 결정한 다음 응답을 보내는 공통 함수를 호출하여 각 함수를 완료하십시오.


2

이 작업을 수행 할 수 있다고 가정하십시오.

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

chain () 만 구현하면 각 함수가 다음 함수에 부분적으로 적용되고 첫 번째 함수 만 즉시 호출됩니다.

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}

안녕 ngn 그리고 답변 주셔서 감사합니다! 필자의 경우 모든 들여 쓰기마다 인라인 클로저 변수에 액세스 할 수 있습니다. 예를 들어 함수는 다음과 같이 작동합니다. HTTP req / res 가져 오기, 쿠키의 DB에서 사용자 ID 가져 오기, 이후 사용자 ID의 전자 메일 가져 오기, 이후 전자 메일의 추가 데이터 가져 오기, ..., 나중에 Y의 경우 X 가져 오기 ... 내가 실수하지 않은 경우, 제안하는 코드는 비동기 함수가 올바른 순서로 실행되도록 보장하지만 모든 함수 본문에서 원래 코드의 클로저로 변수를 자연스럽게 제공 할 수있는 방법이 없습니다. 그 경우입니까?
Kay Pale

2

콜백 지옥은 순수한 자바 스크립트로 쉽게 피할 수 있습니다. 아래 솔루션은 모든 콜백이 함수 (오류, 데이터) 서명을 따른다고 가정합니다.

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});

1

최근에 wait.for 라는 간단한 추상화를 만들어 동기화 모드에서 비동기 함수를 호출했습니다 (Fibres 기반). 초기 단계이지만 작동합니다. 현재 :

https://github.com/luciotato/waitfor

wait.for를 사용하면 표준 nodejs 비동기 함수를 마치 동기 함수 인 것처럼 호출 할 수 있습니다.

wait.for 코드를 사용 하면 다음과 같습니다.

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

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... 또는 덜 장황하게 만들고 싶을 때 (그리고 오류 잡기 추가)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

모든 경우에 getSomeDate , getSomeOtherDategetMoreData 는 마지막 매개 변수 a 함수 콜백 (err, data)을 갖는 표준 비동기 함수 여야합니다.

에서와 같이 :

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}

1

이 문제를 해결하기 위해 JS를 보이지 않게 사전 처리하는 nodent ( https://npmjs.org/package/nodent )를 작성했습니다 . 예제 코드는 (비동기, 실제로-문서를 읽습니다).

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

분명히 많은 다른 솔루션이 있지만 사전 처리에는 런타임 오버 헤드가 거의 없거나 전혀 없다는 이점이 있으며 소스 맵 지원 덕분에 디버깅도 쉽습니다.


0

나는 같은 문제가 있었다. 노드에서 비동기 함수를 실행하는 주요 라이브러리를 보았으므로 코드를 작성하기 위해 자연스럽지 않은 체인 (세 가지 이상의 메소드 conf 등을 사용해야 함)을 제공합니다.

몇 주 동안 간단하고 읽기 쉬운 솔루션을 개발했습니다. EnqJS를 사용해보십시오 . 모든 의견을 부탁드립니다.

대신에:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

EnqJS로 :

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

코드가 이전보다 더 큰 것으로 보입니다. 그러나 이전과 같이 중첩되지 않았습니다. 보다 자연스럽게 보이기 위해 체인을 즉시라고합니다.

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

그리고 함수 내에서 다음과 같이 반환되었습니다.

this.return(response)

0

나는 매우 원시적이지만 효과적인 방법으로 수행합니다. 예를 들어 부모와 자녀가있는 모델을 가져 와서 별도의 쿼리를 수행해야한다고 가정 해 보겠습니다.

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}

0

Fibers https://github.com/laverdet/node-fibers를 사용 하면 비동기 코드가 동기식으로 보입니다 (차단하지 않음)

나는 개인적 으로이 작은 래퍼 http://alexeypetrushin.github.com/synchronize 내 프로젝트의 코드 샘플을 사용합니다 (모든 메소드는 실제로 비동기 적이며 비동기 파일 IO로 작업합니다) 심지어 콜백이나 비동기 제어 흐름 도우미 라이브러리.

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"

0

Task.js 는 다음을 제공합니다.

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

이 대신에 :

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}

0

다른 사람들이 응답 한 후 문제는 지역 변수라고 진술했습니다. 이 작업을 수행하는 쉬운 방법은 로컬 변수를 포함하는 하나의 외부 함수를 작성한 다음 이름이 지정된 내부 함수를 사용하여 이름으로 액세스하는 것입니다. 이런 식으로, 몇 개의 함수를 연결해야하는지에 관계없이 두 개의 깊이 만 중첩합니다.

mysql중첩 을 사용하여 Node.js 모듈 을 사용하려는 초보자의 시도는 다음과 같습니다 .

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

다음은 명명 된 내부 함수를 사용한 재 작성입니다. 외부 함수 with_connection도 지역 변수의 홀더로 사용할 수 있습니다. (여기서, I는 매개 변수있어 sql, bindings, cb유사한 방법으로 그 행동을하지만, 당신은 단지 몇 가지 추가 지역 변수를 정의 할 수 있습니다 with_connection.)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

인스턴스 변수로 객체를 만들고 이러한 인스턴스 변수를 로컬 변수 대신 사용할 수 있다고 생각했습니다. 그러나 이제 중첩 함수와 로컬 변수를 사용하는 위의 접근 방식이 더 간단하고 이해하기 쉽다는 것을 알았습니다. OO를 배우려면 약간의 시간이 걸립니다. :-)

여기 객체와 인스턴스 변수가있는 이전 버전이 있습니다.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

그것은 bind어떤 이점으로 사용될 수 있음 이 밝혀졌습니다 . 메소드 호출에 자신을 전달하는 것 외에는 아무 것도하지 않은 내가 만든 다소 추악한 익명 함수를 제거 할 수 있습니다. 의 잘못된 값과 관련되어 있기 때문에 메소드를 직접 전달할 수 없습니다 this. 그러나을 bind사용하면 this원하는 값을 지정할 수 있습니다 .

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

물론 Node.js 코딩을 사용한 올바른 JS는 없습니다. 방금 몇 시간을 보냈습니다. 그러나 약간의 연마 로이 기술이 도움이 될 수 있습니까?





0

와이어를 사용하면 코드는 다음과 같습니다.

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.