Node.js'de eşzamansız işlevlerin uzun süre iç içe yerleştirilmesi nasıl önlenir


158

Bir DB bazı verileri görüntüleyen bir sayfa yapmak istiyorum, bu yüzden benim DB bu verileri almak bazı işlevler oluşturduk. Ben Node.js'de sadece bir acemi değilim, anladığım kadarıyla, hepsini tek bir sayfada (HTTP yanıtı) kullanmak istiyorsanız hepsini yuvalamak zorundayım:

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

Bunun gibi birçok işlev varsa , yuvalama bir sorun haline gelir .

Bundan sakınmanın bir yolu var mı? Sanırım bu, temel bir şey gibi görünen birden fazla asenkron fonksiyonu nasıl birleştirdiğinizle ilgili.


12
Peki 10 zaman uyumsuz işleviniz olduğunda, 10 girinti seviyeniz var mı?
Kay Pale

Bu bağlantı yardımcı olabilir. stackoverflow.com/a/4631909/290340
Evan Plaice

1
Başka bir sorun: Git satırının okunmasını zorlaştıran ( bundan sonra bile işe yaramaz) birçok satırın girintisini arasına başka bir işlev eklemek getSomeDateve getSomeOtherDatesonlandırmak git blame, ve bunu manuel olarak yaparken muhtemelen hata yaparsınız
Daniel Alder

Yanıtlar:


73

İlginç gözlem. JavaScript'te normalde satır içi anonim geri arama işlevlerini adlandırılmış işlev değişkenleriyle değiştirebileceğinizi unutmayın.

Devamındaki:

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

Böyle bir şeye benzemek için yeniden yazılabilir:

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

Bununla birlikte, başka yerlerde geri arama mantığını yeniden kullanmayı planlamıyorsanız, örneğinizde olduğu gibi satır içi anonim işlevleri okumak genellikle çok daha kolaydır. Ayrıca tüm geri aramalar için bir ad bulmak zorunda kalmazsınız.

Ayrıca , aşağıdaki açıklamada @pst belirtildiği gibi, iç işlevler içindeki kapatma değişkenlerine erişiyorsanız, yukarıdakilerin basit bir çeviri olmayacağını unutmayın. Bu gibi durumlarda, satır içi anonim işlevlerin kullanılması daha da çok tercih edilir.


26
Ancak, un-iç içe zaman, değişkenler üzerinde bir miktar kapatma semantik (ve gerçekten sadece ticaret-off anlamak) olabilir doğrudan bir çeviri değil bu yüzden kaybolabilir. Yukarıdaki örnekte 'res' in'e erişim getMoreDatakaybedilmiştir.

2
Bence çözümünüz bozuldu: someDataParserTÜM verileri ayrıştırıyor, çünkü aynı zamanda çağırıyor getMoreData. Bu anlamda işlev adı yanlıştır ve yuvalama sorununu gerçekten kaldırmamış olduğumuz anlaşılmaktadır.
Konstantin Schubert

63

Kay, sadece bu modüllerden birini kullan.

Bunu döndürecek:

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

Bunun içine:

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 ve async'e hızlıca baktım ve sadece fonksiyon yürütme sırası ile ilgileniyorlar gibi görünüyor. Benim durumumda, her girintide satır içi kapatma değişkenlerine erişim vardır. Örneğin işlevler şu şekilde çalışır: HTTP req / res olsun, çerez için DB'den userid alın, sonraki kullanıcı kimliği için e-posta alın, sonraki e-posta için daha fazla veri alın, ..., Y için X alın, ... Yanılmıyorsam, bu çerçeveler yalnızca zaman uyumsuz işlevlerin doğru sırayla yürütülmesini sağlar, ancak her işlev gövdesinde değişkenler tarafından doğal olarak sağlanan değişkeni elde etmenin yolu yoktur (?) Teşekkürler :)
Kay Pale

9
Bu kütüphaneleri sıralamak açısından, Github'daki her birinde "Yıldız" sayısını kontrol ettim. async yaklaşık 3000 ile en fazla, Step yaklaşık 1000 ile, diğerleri önemli ölçüde daha az. Tabii ki, hepsi aynı şeyi yapmıyor :-)
kgilpin

3
@KayPale async.waterfall kullanma eğilimindeyim ve bazen her adım / adım için bir sonraki adımın gereksiniminden geçecek olan kendi işlevlerim olur veya async.METHOD çağrısından önce değişkenleri tanımlayarak alt satırda kullanılabilir olmasını sağlar. Ayrıca zaman uyumsuz. * Çağrıları için METHODNAME.bind (...) kullanacak, bu da oldukça iyi çalışıyor.
Tracker1

Kısa bir soru: Modül listenizde son ikisi aynı mı? Yani "async.js" ve "async"
dari0h

18

Çoğunlukla Daniel Vassallo ile hemfikirim. Karmaşık ve derin yuvalanmış bir işlevi ayrı adlandırılmış işlevlere ayırabilirseniz, bu genellikle iyi bir fikirdir. Tek bir işlev içinde yapmanın mantıklı olduğu zamanlarda, kullanılabilen birçok node.js zaman uyumsuz kitaplığından birini kullanabilirsiniz. İnsanlar bununla başa çıkmak için birçok farklı yol bulmuşlardır, bu nedenle node.js modülleri sayfasına bir göz atın ve ne düşündüğünüzü görün.

Bunun için async.js adlı bir modül yazdım . Bunu kullanarak, yukarıdaki örnek şu şekilde güncellenebilir:

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

Bu yaklaşımla ilgili güzel bir şey, 'seri' işlevini 'paralel' olarak değiştirerek verileri paralel almak için kodunuzu hızla değiştirebilmenizdir. Dahası, async.js de tarayıcının içinde çalışacaktır, bu nedenle herhangi bir zor async kodu ile karşılaşırsanız node.js ile aynı yöntemleri kullanabilirsiniz.

Umarım faydalıdır!


Merhaba Caolan ve cevap için teşekkürler! Benim durumumda, her girintide satır içi kapatma değişkenlerine erişim vardır. Örneğin işlevler şu şekilde çalışır: HTTP req / res olsun, çerez için DB'den userid alın, sonraki kullanıcı kimliği için e-posta alın, sonraki e-posta için daha fazla veri alın, ..., Y için X alın, ... Yanılmıyorsam, önerdiğiniz kod yalnızca zaman uyumsuz işlevlerin doğru sırayla yürütülmesini sağlar, ancak her işlev gövdesinde, orijinal kodumdaki kapaklar tarafından sağlanan değişkeni doğal olarak almanın yolu yoktur. Durum bu mu?
Kay Pale

3
Ulaşmaya çalıştığınız şeye mimari olarak veri hattı denir. Bu gibi durumlar için zaman uyumsuz şelaleyi kullanabilirsiniz.
Rudolf Meijering

18

Bu hileyi iç içe işlevler veya bir modül yerine bir dizi ile kullanabilirsiniz.

Gözlerde çok daha kolay.

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

Paralel süreçler veya hatta süreçlerin paralel zincirleri için deyimi genişletebilirsiniz:

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

Bu amaçla async.js'yi çok seviyorum .

Sorun şelale komutu ile çözüldü:

şelale (görevler, [geri arama])

Her biri sonuçlarını dizideki bir sonrakine geçiren bir dizi dizi işlevi çalıştırır. Ancak, işlevlerden herhangi biri geri aramaya bir hata iletirse, sonraki işlev yürütülmez ve ana geri arama derhal hatayla çağrılır.

Argümanlar

görevleri - Çalıştırılacak bir işlevler dizisi, her işlev tamamlandığında çağırması gereken bir geri arama (err, sonuç1, sonuç2, ...) geçirilir. İlk argüman bir hatadır (boş olabilir) ve diğer argümanlar bir sonraki göreve yönelik olarak argümanlar olarak iletilir. callback (err, [results]) - Tüm işlevler tamamlandıktan sonra çalıştırılacak isteğe bağlı bir geri arama. Bu, son görevin geri çağrısının sonuçlarını geçirecektir.

Misal

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 değişkenlerine gelince, bunlar async.waterfall çağrısının tamamını kapsayan işlev (req, res) {} ile aynı kapsamda paylaşılacaktır.

Sadece bu değil, asenkron çok temiz. Demek istediğim şu gibi birçok durumu değiştirmem:

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

İlk olarak:

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

Sonra buna:

function2(o,cb);

Sonra buna:

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

Ayrıca, zaman uyumsuzluk için hazırlanan birçok hazır fonksiyonun util.js'den çok hızlı çağrılmasını sağlar. Sadece yapmak istediğiniz şeyi zincirleyin, o, cb'nin evrensel olarak ele alındığından emin olun. Bu, tüm kodlama sürecini çok hızlandırır.


11

İhtiyacınız olan şey biraz sözdizimsel şeker. Şuna bir bak:

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

Oldukça temiz , değil mi? HTML'nin bir dizi haline geldiğini fark edebilirsiniz. Bunun nedeni kısmen dizelerin değişmez olmasıdır; bu nedenle, çıktınızı daha büyük ve daha büyük dizeleri atmak yerine bir dizide arabelleğe almanız daha iyi olur. Diğer sebep ise bir başka güzel sözdizimidir bind.

Queueörnekte gerçekten sadece bir örnek ve birlikte partialaşağıdaki gibi uygulanabilir

// 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 (), eşzamansız çağrıların sonuçlarını beklemeden kısmi parçaları birbiri ardına yürütür.
ngn

Spot, teşekkürler. Cevabı güncelledim. İşte bir test: jsbin.com/ebobo5/edit (isteğe bağlı bir lastişlevle)
gblazex

Merhaba galambalazs ve cevap için teşekkürler! Benim durumumda, her girintide satır içi kapatma değişkenlerine erişim vardır. Örneğin işlevler şu şekilde çalışır: HTTP req / res olsun, çerez için DB'den userid alın, sonraki kullanıcı kimliği için e-posta alın, sonraki e-posta için daha fazla veri alın, ..., Y için X alın, ... Yanılmıyorsam, önerdiğiniz kod yalnızca zaman uyumsuz işlevlerin doğru sırada yürütülmesini sağlar, ancak her işlev gövdesinde, orijinal kodumdaki kapaklar tarafından sağlanan değişkeni doğal olarak almanın yolu yoktur. Durum bu mu?
Kay Pale

1
Peki tüm cevaplarda kesinlikle kapanışları kaybedersiniz. Yapabileceğiniz şey, paylaşılan veriler için global kapsamda bir nesne oluşturmaktır . Böylece, örneğin ilk işleviniz eklenir obj.emailve sonraki işleviniz kullanır, obj.emailardından onu siler (veya yalnızca atar null).
gblazex

7

Bulduğumdan beri Async.js'ye aşığım . async.seriesUzun yuvalamayı önlemek için kullanabileceğiniz bir işlevi vardır .

Belgeler: -


serisi (görevler, [geri arama])

Her biri önceki işlev tamamlandıktan sonra çalışan bir dizi dizi işlev çalıştırın. [...]

Argümanlar

tasks- Çalıştırılacak bir fonksiyon dizisi, her fonksiyon tamamlandığında çağırması gereken bir geri çağırma geçirilir. callback(err, [results])- Tüm fonksiyonlar tamamlandıktan sonra çalıştırmak için isteğe bağlı bir geri arama. Bu işlev, dizide kullanılan geri çağrılara iletilen tüm bağımsız değişkenlerin bir dizisini alır.


Örnek kodunuza nasıl uygulayacağımız aşağıda açıklanmıştır: -

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

Gördüğüm en basit sözdizimsel şeker düğüm vaadidir.

npm yükleme düğümü vaadi || git clone https://github.com/kriszyp/node-promise

Bunu kullanarak, zaman uyumsuz yöntemleri zincirleyebilirsiniz:

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

Her birinin dönüş değeri bir sonraki bölümde bağımsız değişken olarak kullanılabilir.


3

Yaptığınız şey bir asenkron desen almak ve bunu sırayla denilen 3 işleve uygulamak, her biri başlamadan önce bir öncekinin tamamlanmasını bekliyor - yani onları senkronize ettiniz . Asenkron programlama ile ilgili nokta, hepsi aynı anda çalışan birçok işleve sahip olmanız ve her birinin tamamlanmasını beklemeniz gerekmemesidir.

getSomeDate (), getMoreData () için hiçbir şey sağlamayan getSomeOtherDate () için hiçbir şey sağlamazsa, neden bunları js izin verdiği gibi eşzamansız olarak çağırmıyorsunuz veya birbirine bağımlıysa (ve eşzamansız değil) tek işlev?

Akışı kontrol etmek için iç içe yerleştirmeyi kullanmanıza gerek yoktur - örneğin, her üçünün de ne zaman tamamlanacağını belirleyen ve ardından yanıtı gönderdiği ortak bir işlevi çağırarak her bir işlevin bitmesini sağlayın.


2

Bunu yapabileceğinizi varsayalım:

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 () işlevini uygulamanız yeterlidir, böylece her işlevi bir sonrakine kısmen uygular ve hemen yalnızca ilk işlevi çağırır:

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

Merhaba ngn ve cevap için teşekkürler! Benim durumumda, her girintide satır içi kapatma değişkenlerine erişim vardır. Örneğin işlevler şu şekilde çalışır: HTTP req / res olsun, çerez için DB'den userid alın, sonraki kullanıcı kimliği için e-posta alın, sonraki e-posta için daha fazla veri alın, ..., Y için X alın, ... Yanılmıyorsam, önerdiğiniz kod yalnızca zaman uyumsuz işlevlerin doğru sırada yürütülmesini sağlar, ancak her işlev gövdesinde orijinal kodumdaki kapaklar tarafından sağlanan değişkeni doğal olarak almanın yolu yoktur. Durum bu mu?
Kay Pale

2

saf javascript kapatılması ile geri çağrı cehennem kolayca önlenebilir. Aşağıdaki çözüm, tüm geri çağrıların işlev (hata, veri) imzasını izlediğini varsayar.

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

Yakın zamanda senkronizasyon modunda (Fiberlere dayalı) zaman uyumsuz işlevleri çağırmak için wait.for adı verilen daha basit bir soyutlama oluşturdum . Erken bir aşamada ama işe yarıyor. Şurada:

https://github.com/luciotato/waitfor

Wait.for komutunu kullanarak , herhangi bir standart nodejs async işlevini, sanki bir eşitleme işleviymiş gibi çağırabilirsiniz.

wait.for kodunuzu kullanarak :

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

... veya daha az ayrıntılı olmak istiyorsanız (ve ayrıca hata yakalama eklemek istiyorsanız)

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

};

Tüm durumlarda, getSomeDate , getSomeOtherDate ve getMoreData bir işlev geri çağırma (err, veri) son parametresi ile standart zaman uyumsuz işlevler olmalıdır

de olduğu gibi:

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

Bu sorunu çözmek için JS'nizi görünmez bir şekilde önceden işleyen nodent ( https://npmjs.org/package/nodent ) yazdım . Örnek kodunuz (async, gerçekten - dokümanları okuyun) olur.

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

Açıkçası, başka birçok çözüm var, ancak ön işleme, çalışma zamanı yükü çok az veya hiç yok olma avantajına sahiptir ve kaynak harita desteği sayesinde hata ayıklamak kolaydır.


0

Ben de aynı problemi yaşadım. Düğüm async fonksiyonları çalıştırmak için büyük libs gördüm ve onlar kodunuzu oluşturmak için çok doğal olmayan zincirleme (üç veya daha fazla yöntem confs vb kullanmanız gerekir) sunar.

Basit ve okunması kolay bir çözüm geliştirmek için birkaç hafta geçirdim. Lütfen EnqJS'yi deneyin . Tüm görüşler takdir edilecektir.

Onun yerine:

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 ile:

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

Kodun öncekinden daha büyük göründüğüne dikkat edin. Ama eskisi gibi iç içe değil. Daha doğal görünmek için, zincirler hemen çağrılır:

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

Ve geri döndüğünü söylemek için, dediğimiz fonksiyonun içinde:

this.return(response)

0

Bunu oldukça ilkel ama etkili bir şekilde yapıyorum. Örneğin, ebeveynleri ve çocukları ile bir model almam gerekiyor ve diyelim ki onlar için ayrı sorgular yapmalıyım:

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

Lifler kullanın https://github.com/laverdet/node-fibers zaman uyumsuz kodun senkron gibi görünmesini sağlar (engellemeden)

Şahsen bu küçük sarmalayıcıyı kullanıyorum http://alexeypetrushin.github.com/synchronize Projemden kod örneği (her yöntem aslında eşzamansız, eşzamansız dosya IO ile çalışıyor) Hatta geri arama ile ne karışıklık olacağını hayal etmekten korkuyorum asenkron-kontrol-akış yardımcı kütüphaneleri.

_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 size şunları sunar:

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

Bunun yerine:

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

Diğerleri yanıtladıktan sonra, sorunun yerel değişkenler olduğunu söylediniz. Bunu yapmanın kolay bir yolu, bu yerel değişkenleri içermek için bir dış fonksiyon yazmak, daha sonra adlandırılmış iç fonksiyonlar kullanmak ve isimlere erişmek. Bu şekilde, birlikte zincirlemeniz gereken fonksiyonlardan bağımsız olarak, yalnızca iki derin yuva yaparsınız.

Yeni başlayanımın mysqlNode.js modülünü iç içe yerleştirme ile kullanma girişimi :

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

Aşağıda, adlandırılmış iç işlevler kullanılarak bir yeniden yazma yer almaktadır. Dış fonksiyon with_connectionyerel değişkenler için de tutucu olarak kullanılabilir. (İşte, parametreleri var sql, bindings, cbbenzer şekilde bu eylemi, ama sadece bazı ek yerel değişkenler tanımlayabilirsiniz 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);
}

Belki de örnek değişkenleri olan bir nesne yapmak ve bu örnek değişkenleri yerel değişkenlerin yerine kullanmak mümkün olabileceğini düşünüyordum. Ancak şimdi, iç içe geçmiş işlevleri ve yerel değişkenleri kullanan yukarıdaki yaklaşımın daha basit ve anlaşılması daha kolay olduğunu düşünüyorum. OO'nun öğrenilmesi biraz zaman alıyor, öyle görünüyor :-)

İşte bir nesne ve örnek değişkenleri ile önceki sürümüm.

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

bindBazı avantajlar için kullanılabileceği ortaya çıktı . Kendilerini bir yöntem çağrısına iletmek dışında, hiçbir şey yapmayan, biraz çirkin anonim işlevlerden kurtulmama izin veriyor. Yöntemi doğrudan geçemedim çünkü yanlış değerine karışmış olurdu this. Ama ile istediğim binddeğeri belirtebilirim 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);
}

Tabii ki, bunların hiçbiri Node.js kodlaması ile uygun JS değil - sadece birkaç saat geçirdim. Ama belki biraz parlatma ile bu teknik yardımcı olabilir?





0

Kablo kullanarak kodunuz şöyle görünecektir:

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

0

bildiğiniz için Jazz.js'yi düşünün https://github.com/Javanile/Jazz.js/wiki/Script-showcase

    const jj = gerektirir ('jazz.js');

    // ultra uyumlu yığın
    jj.script ([
        a => ProcessTaskOneCallbackAtEnd (a),
        b => ProcessTaskTwoCallbackAtEnd (b),
        c => ProcessTaskThreeCallbackAtEnd (c),
        d => ProcessTaskFourCallbackAtEnd (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.