การเปรียบเทียบไลบรารีแบบอะซิงโครนัส Node.js - Q กับ Async

ฉันใช้ ไลบรารี Q ของ kriskowal สำหรับโปรเจ็กต์ (เครื่องขูดเว็บ / โปรแกรมจำลองกิจกรรมของมนุษย์) และได้คุ้นเคยกับคำสัญญา โดยส่งคืนและ การแก้ไข/ปฏิเสธสิ่งเหล่านั้น และวิธีการควบคุมโฟลว์แบบอะซิงโครนัสพื้นฐานของไลบรารีและกลไกการโยน/จับข้อผิดพลาดได้รับการพิสูจน์แล้วว่าจำเป็น

ฉันพบปัญหาบางอย่างแล้ว การโทร promise.then ของฉันและการโทรกลับของฉันมีแนวโน้มที่จะก่อตัวเป็นปิรามิด บางครั้งอาจเป็นเพราะเหตุผลในการกำหนดขอบเขต แต่บางครั้งก็เพื่อรับประกันลำดับเหตุการณ์ที่แน่นอน (ฉันคิดว่าฉันอาจแก้ไขปัญหาเหล่านี้ได้ด้วยการปรับโครงสร้างใหม่ แต่ต่อจากนี้ไปฉันต้องการหลีกเลี่ยง "นรกโทรกลับ" โดยสิ้นเชิง)

นอกจากนี้การดีบักยังน่าหงุดหงิดมาก ฉันใช้เวลามากมายในการ console.log เพื่อค้นหาแหล่งที่มาของข้อผิดพลาดและจุดบกพร่อง หลังจากที่ฉันพบข้อผิดพลาดเหล่านั้นในที่สุด ฉันจะเริ่มโยนข้อผิดพลาดไปที่นั่นและจับข้อผิดพลาดเหล่านั้นที่อื่นด้วย promise.finally แต่กระบวนการในการค้นหาข้อผิดพลาดในตอนแรกนั้นลำบาก

นอกจากนี้ ในโครงการของฉัน คำสั่งซื้อมีความสำคัญ ฉันต้องทำทุกอย่างตามลำดับ บ่อยครั้งที่ฉันพบว่าตัวเองสร้างอาร์เรย์ของฟังก์ชันที่ส่งคืนสัญญา จากนั้นเชื่อมโยงเข้าด้วยกันโดยใช้ Array.prototype.reduce ซึ่งฉันไม่คิดว่าควรทำ

นี่คือตัวอย่างหนึ่งในวิธีการของฉันที่ใช้เทคนิคการลดขนาดนี้:

removeItem: function (itemId) {

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);

  return this.getPage('/stock.php')
  .then(function (webpage) {
    var
      pageCount = 5,
      promiseFunctions = [],
      promiseSequence;

    // Create an array of promise-yielding functions that can run sequentially.
    _.times(pageCount, function (i) {
      var promiseFunction = function () {
        var
          promise,
          path;

        if (i === 0) {
          promise = Q(webpage);
        } else {
          path = '/stock.php?p=' + i;
          promise = this.getPage(path);
        }

        return promise.then(function (webpage) {
          var
            removeMatch = webpage.match(removeRegexp),
            removePath;

          if (removeMatch !== null) {
            removePath = removeitemMatch[0];

            return this.getPage(removePath)
            .delay(1000)
            // Stop calling subsequent promises.
            .thenResolve(true);
          }

          // Don't stop calling subsequent promises.
          return false;

        }.bind(this));
      }.bind(this);

      promiseFunctions.push(promiseFunction);
    }, this);

    // Resolve the promises sequentially but stop early if the item is found.
    promiseSequence = promiseFunctions.reduce(function (soFar, promiseFunction, index) {
      return soFar.then(function (stop) {
        if (stop) {
          return true;
        } else {
          return Q.delay(1000).then(promiseFunction);
        }
      });
    }, Q());

    return promiseSequence;
  }.bind(this))
  .fail(function (onRejected) {
    console.log(onRejected);
  });
},

ฉันมีวิธีอื่นที่ทำสิ่งเดียวกันโดยพื้นฐานแล้ว แต่ต้องทนทุกข์ทรมานจากปัญหาการเยื้องที่แย่กว่ามาก

ฉันกำลังพิจารณาปรับโครงสร้างโครงการใหม่โดยใช้ไลบรารี async ของ coalan ดูเหมือนคล้ายกับ Q แต่ฉันอยากรู้ว่ามันแตกต่างกันอย่างไร ความประทับใจที่ฉันได้รับคือ async "callback-centric" มากกว่าในขณะที่ Q คือ "promise-centric"

คำถาม: เมื่อพิจารณาถึงปัญหาและข้อกำหนดของโครงการ ฉันจะได้รับและ/หรือสูญเสียอะไรจากการใช้ async บน Q ห้องสมุดเปรียบเทียบกันอย่างไร? (โดยเฉพาะอย่างยิ่งในแง่ของการดำเนินการชุดของงานตามลำดับและการดีบัก/การจัดการข้อผิดพลาด)


person Jackson    schedule 19.03.2014    source แหล่งที่มา
comment
การจำเป็นต้องมีการดำเนินการตามลำดับดูเหมือนจะทำให้ประโยชน์ส่วนใหญ่ของ async เป็นโมฆะ   -  person Robert Harvey    schedule 19.03.2014
comment
ผู้คนอาจแนะนำคุณได้ดีขึ้นหากคุณแสดงโค้ดที่เทอะทะโดยเฉพาะอย่างยิ่งที่คุณใช้อยู่ตอนนี้ซึ่งคุณต้องการวิธีแก้ปัญหาที่ดีกว่า การพูดคุยถึงข้อดี/ข้อเสียของห้องสมุดต่างๆ หรือวิธีการใช้ห้องสมุดเหล่านั้นจะยากกว่ามากในเชิงนามธรรม   -  person jfriend00    schedule 19.03.2014
comment
@ jfriend00 ฉันเห็นด้วย; ฉันได้เพิ่มตัวอย่างโค้ดแล้ว   -  person Jackson    schedule 19.03.2014
comment
ปิรามิดสามารถถูกย่อ/ลบออกได้โดยใช้ .then อย่างเหมาะสม ในทางกลับกัน ไลบรารี async มีหลายวิธีที่อาจใช้สำหรับการโทรแบบอะซิงโครนัสแบบซิงโครนัส เช่น .series และ .eachSeries แน่นอนว่า .then chain สามารถบรรลุเป้าหมายเดียวกันได้   -  person Kevin B    schedule 04.08.2014
comment
@Robert Harvey - ในโหนด async ยังคงมีคุณค่าเนื่องจากทำให้คำขออื่นมีโอกาสได้รับการประมวลผล   -  person Robert Levy    schedule 04.08.2014
comment
คุณได้ตรวจสอบ async-q แล้วหรือยัง? มันเป็นตัวห่อสัญญา (โดยใช้ Q) รอบไลบรารี async ของ Coalan โดยพื้นฐานแล้วจะเพิ่มโครงสร้างการควบคุมเพิ่มเติมสำหรับโค้ดอะซิงโครนัส เช่น map, ลด, ซีรีส์ ฯลฯ: npmjs.com/package /async-q   -  person slebetman    schedule 30.09.2016
comment
@slebetman เจ๋ง! ฉันอยากลองตอนนี้เลย   -  person Jackson    schedule 30.09.2016


คำตอบ (3)


ห้องสมุดทั้งสองแห่งก็ดี ฉันได้ค้นพบว่าสิ่งเหล่านี้มีจุดประสงค์ที่แยกจากกันและสามารถใช้ควบคู่กันได้

Q ให้อ็อบเจ็กต์สัญญาแก่นักพัฒนา ซึ่งเป็นตัวแทนของคุณค่าในอนาคต มีประโยชน์สำหรับการเดินทางข้ามเวลา

Async ช่วยให้นักพัฒนามีโครงสร้างการควบคุมเวอร์ชันอะซิงโครนัสและการดำเนินการรวม

ตัวอย่างจากความพยายามครั้งเดียวในการใช้งาน linter แสดงให้เห็นถึงความสามัคคีที่อาจเกิดขึ้นระหว่างไลบรารี:

function lint(files, callback) {

    // Function which returns a promise.
    var getMerged = merger('.jslintrc'),

        // Result objects to invoke callback with.
        results = [];

    async.each(files, function (file, callback) {
        fs.exists(file, function (exists) {

            // Future representation of the file's contents.
            var contentsPromise,

                // Future representation of JSLINT options from .jslintrc files.
                optionPromise;

            if (!exists) {
                callback();
                return;
            }

            contentsPromise = q.nfcall(fs.readFile, file, 'utf8');
            optionPromise = getMerged(path.dirname(file));

            // Parallelize IO operations.
            q.all([contentsPromise, optionPromise])
                .spread(function (contents, option) {
                    var success = JSLINT(contents, option),
                        errors,
                        fileResults;
                    if (!success) {
                        errors = JSLINT.data().errors;
                        fileResults = errors.reduce(function (soFar, error) {
                            if (error === null) {
                                return soFar;
                            }
                            return soFar.concat({
                                file: file,
                                error: error
                            });
                        }, []);
                        results = results.concat(fileResults);
                    }
                    process.nextTick(callback);
                })
                .catch(function (error) {
                    process.nextTick(function () {
                        callback(error);
                    });
                })
                .done();
        });
    }, function (error) {
        results = results.sort(function (a, b) {
            return a.file.charCodeAt(0) - b.file.charCodeAt(0);
        });
        callback(error, results);
    });
}

ฉันต้องการทำสิ่งที่อาจบล็อกแต่ละไฟล์ ดังนั้น async.each จึงเป็นตัวเลือกที่ชัดเจน ฉันสามารถขนานการดำเนินการ ที่เกี่ยวข้อง ต่อการวนซ้ำ ด้วย q.all และใช้ค่าตัวเลือกของฉันซ้ำได้หากใช้กับไฟล์ 2 ไฟล์ขึ้นไป

ในที่นี้ Async และ Q ต่างก็มีอิทธิพลต่อโฟลว์การควบคุมของโปรแกรม และ Q แสดงถึงค่าที่จะแก้ไขเนื้อหาไฟล์ในอนาคต ห้องสมุดทำงานร่วมกันได้ดี ไม่จำเป็นต้อง "เลือกอย่างใดอย่างหนึ่งมากกว่ากัน"

person Jackson    schedule 12.11.2014

พีระมิดการเรียกกลับในโค้ดของคุณสามารถทำให้ง่ายขึ้นได้โดยใช้องค์ประกอบสัญญาและการกำหนดขอบเขตคำศัพท์จาวาสคริปต์

removeItem: function (itemId) {

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);
  var found = false
  var promise = getPage('/sock.php')

  _.times(5, (i) => {
    promise = promise.then((webpage) => {
      if (found) return true
      var removeMatch = webpage.match(removeRegexp)
      var found = removeMath !== null
      var nextPage = found ? removeMatch[0] : '/stock.php?p='+i+1
      return Q.delay(1000).then(() => this.getPage(nextPage))
    })
  })

  return promise.fail(console.log.bind(console))

},

ไม่ควรใช้ IMHO async ในโค้ดจาวาสคริปต์ใหม่ คำสัญญาสามารถเขียนได้ง่ายกว่าและอนุญาตให้มีโค้ดที่ใช้งานง่ายมากขึ้น

สาเหตุหลักว่าทำไมโหนดไม่ได้ใช้สัญญาก็เนื่องมาจากข้อกังวลด้านประสิทธิภาพซึ่งส่วนใหญ่ได้รับการแก้ไขเป็นอย่างดีโดยไลบรารีเช่น Bluebird และ Q

เนื่องจากไวยากรณ์ async/await กลายเป็นกระแสหลักมากขึ้น คำมั่นสัญญาจะปูทางสำหรับโค้ดที่ดูคล้ายกับโค้ดซิงโครนัสมาก

person lorefnon    schedule 19.12.2015

แม้ว่านี่จะยังไม่ใช่คำตอบที่แท้จริงสำหรับ คำถาม ของฉัน (Q เทียบกับ async) เกี่ยวกับ ปัญหา ของฉัน ฉันพบ Selenium / WebDriverJs เพื่อเป็นโซลูชันที่ใช้งานได้

driver.get('http://www.google.com');
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
driver.findElement(webdriver.By.name('btnG')).click();
driver.wait(function() {
  return driver.getTitle().then(function(title) {
    return title === 'webdriver - Google Search';
  });
}, 1000);

WebDriver ใช้คิวเพื่อดำเนินการสัญญาตามลำดับ ซึ่งช่วยได้อย่างมากในการควบคุมการเยื้อง คำสัญญาของมันยังเข้ากันได้กับ Q's

การสร้างลำดับคำสัญญาจะไม่ใช่ปัญหาอีกต่อไป for-loop แบบธรรมดาจะทำได้

สำหรับการหยุดตั้งแต่เนิ่นๆ อย่าทำเช่นนี้ แทนที่จะใช้ลำดับ ให้ใช้การออกแบบและสาขาแบบอะซิงโครนัสในขณะที่

person Jackson    schedule 03.08.2014
comment
ฉันต้องบอกว่าฉันไม่เคยคิดที่จะใช้ซีลีเนียมในการขูด แต่ฉันใช้มันเพื่อทดสอบเป็นตัวจำลองกิจกรรมของมนุษย์ซึ่งน่าสนใจ นอกจากนี้สัญญาของ webdriver ยังเข้ากันไม่ได้กับ Q's พวกมันประเมินแตกต่างออกไปและอาจให้ผลบวกลวงแก่คุณได้ หากคุณต้องการสร้างสัญญาที่กำหนดเอง ให้ใช้ webdriver.promise() - person RadleyMith; 21.01.2015