ยังมีเหตุผลที่จะใช้ห้องสมุดสัญญาเช่น Q หรือ BlueBird ตอนนี้ที่เรามีสัญญา ES6 หรือไม่? [ปิด]


228

หลังจาก Node.js เพิ่มการสนับสนุนแบบเนทีฟสำหรับคำสัญญาแล้วยังมีเหตุผลที่จะใช้ห้องสมุดอย่าง Q หรือ BlueBird หรือไม่?

ตัวอย่างเช่นหากคุณกำลังเริ่มโครงการใหม่และสมมติว่าในโครงการนี้คุณไม่มีการอ้างอิงใด ๆ ที่ใช้ไลบรารีเหล่านี้เราสามารถพูดได้หรือไม่ว่าไม่มีเหตุผลที่จะใช้ไลบรารีดังกล่าวอีกต่อไป?


4
สัญญาพื้นเมืองมีคุณสมบัติพื้นฐานมาก ไลบรารี่อย่าง Q หรือ Bluebird เพิ่มจำนวนมากขึ้น หากคุณต้องการคุณสมบัติเหล่านั้นให้ใช้ไลบรารีเหล่านั้น
gman

7
ฉันแก้ไขชื่อเพื่อให้น้อยลงเกี่ยวกับ "ความต้องการ" และอีกมากมายเกี่ยวกับ "เหตุผลในการใช้ห้องสมุดสัญญา" คำถามนี้สามารถตอบได้โดยการให้ข้อเท็จจริงเป็นหลักไม่ใช่ความคิดเห็น ควรเปิดใหม่เพราะสามารถตอบได้ด้วยการให้ข้อเท็จจริงและไม่ให้ความเห็นเป็นหลัก ดูคำตอบด้านล่างเป็นการสาธิตสิ่งนั้น
jfriend00

11
@JaromandaX - โปรดลองเปิดใหม่อีกครั้งว่าชื่อและคำถามนั้นได้รับการปรับแต่งเพื่อให้มากขึ้นเกี่ยวกับสาเหตุที่คนเราจะใช้ห้องสมุดสัญญามากกว่าว่าจะมีใคร "ต้องการ" เพื่อใช้ห้องสมุดสัญญา ในความคิดของฉันคำถามนี้สามารถตอบได้โดยการให้ข้อเท็จจริงและไม่ใช่ความเห็นเป็นหลัก - ดูคำตอบด้านล่างเป็นการแสดงให้เห็นว่า
jfriend00

6
คำถามนี้หลังจากการแก้ไขชื่อและคำตอบที่ยอมรับแล้วไม่ได้อิงตามความคิดเห็น
สูงสุด

7
ตกลง นี่เป็นคำถามที่ถูกต้องสมบูรณ์ในรูปแบบปัจจุบัน ฉันได้รับการเสนอชื่อเพื่อเปิดใหม่
Jules

คำตอบ:


367

สุภาษิตโบราณไปว่าคุณควรเลือกเครื่องมือที่เหมาะสมสำหรับงาน ES6 สัญญาว่าจะให้ข้อมูลเบื้องต้น หากทุกสิ่งที่คุณต้องการหรือจำเป็นต้องมีคือพื้นฐานสิ่งนั้นก็ควรจะใช้ได้ดีสำหรับคุณ แต่มีเครื่องมือในถังขยะเครื่องมือมากกว่าพื้นฐานและมีสถานการณ์ที่เครื่องมือเพิ่มเติมเหล่านั้นมีประโยชน์มาก และฉันขอยืนยันว่าสัญญา ES6 นั้นยังขาดพื้นฐานบางอย่างเช่นการทำสัญญาที่มีประโยชน์ในทุกโครงการของ node.js

ฉันคุ้นเคยมากที่สุดกับห้องสมุดสัญญาของ Bluebirdดังนั้นฉันจะพูดส่วนใหญ่จากประสบการณ์ของฉันกับห้องสมุดนั้น

ดังนั้นนี่คือเหตุผล 6 อันดับแรกของฉันในการใช้ห้องสมุดสัญญาที่มีความสามารถมากกว่า

  1. อินเตอร์เฟส async ที่ไม่ได้รับการสัญญา - .promisify()และ.promisifyAll()มีประโยชน์อย่างเหลือเชื่อในการจัดการอินเทอร์เฟซ async ทั้งหมดที่ยังคงต้องการการโทรกลับธรรมดาและยังไม่ส่งคืนสัญญา - โค้ดหนึ่งบรรทัดสร้างเวอร์ชันที่ได้รับการรับรองของอินเตอร์เฟสทั้งหมด

  2. เร็วกว่า - Bluebird เร็วกว่าคำมั่นสัญญาดั้งเดิมในสภาพแวดล้อมส่วนใหญ่อย่างมีนัยสำคัญ

  3. การเรียงลำดับของการทำซ้ำอาร์เรย์ของ async - Promise.mapSeries()หรือPromise.reduce()อนุญาตให้คุณวนซ้ำผ่านอาร์เรย์เรียกการดำเนินการ async ในแต่ละองค์ประกอบ แต่เรียงลำดับการดำเนินการของ async เพื่อให้เกิดการเรียงตัวกันทีละรายการไม่ใช่ทั้งหมดในเวลาเดียวกัน คุณสามารถทำสิ่งนี้ได้เนื่องจากเซิร์ฟเวอร์ปลายทางต้องการหรือเพราะคุณต้องส่งผลลัพธ์หนึ่งรายการต่อไป

  4. Polyfill - หากคุณต้องการใช้สัญญาในเบราว์เซอร์ไคลเอนต์รุ่นเก่าคุณจะต้องใช้ polyfill อยู่ดี อาจได้รับ polyfill ที่มีความสามารถเช่นกัน เนื่องจาก node.js มีสัญญา ES6 คุณไม่จำเป็นต้องใช้ polyfill ใน node.js แต่คุณอาจใช้เบราว์เซอร์ หากคุณกำลังเข้ารหัสทั้งเซิร์ฟเวอร์ node.js และไคลเอนต์อาจเป็นประโยชน์อย่างมากที่จะมีไลบรารี่และฟีเจอร์ที่เหมือนกันทั้งคู่ (ง่ายต่อการแชร์รหัสสลับบริบทระหว่างสภาพแวดล้อมใช้เทคนิคการเข้ารหัสทั่วไปสำหรับรหัส async ฯลฯ .)

  5. คุณสมบัติที่เป็นประโยชน์อื่น ๆ - ครามมีPromise.map(), Promise.some(), Promise.any(), Promise.filter(), Promise.each()และPromise.props()ทั้งหมดที่มีบางครั้งที่มีประโยชน์ ในขณะที่การดำเนินการเหล่านี้สามารถดำเนินการได้ด้วยสัญญา ES6 และรหัสเพิ่มเติม Bluebird มาพร้อมกับการดำเนินการเหล่านี้ที่สร้างไว้ล่วงหน้าและทดสอบล่วงหน้าแล้วดังนั้นจึงเป็นเรื่องง่ายและรหัสน้อยกว่าที่จะใช้

  6. คำเตือนในตัวและการติดตามสแต็คเต็มรูปแบบ - Bluebird มีคำเตือนในตัวหลายตัวที่แจ้งเตือนคุณถึงปัญหาที่อาจเป็นรหัสผิดหรือข้อผิดพลาด ตัวอย่างเช่นหากคุณเรียกใช้ฟังก์ชันที่สร้างสัญญาใหม่ภายใน.then()ตัวจัดการโดยไม่ส่งคืนสัญญานั้น (เพื่อเชื่อมโยงสัญญานั้นเข้ากับห่วงโซ่สัญญาปัจจุบัน) ในกรณีส่วนใหญ่นั่นคือข้อผิดพลาดโดยไม่ตั้งใจและ Bluebird จะเตือนคุณ ผล อื่น ๆ ในตัวคำเตือนครามมีการอธิบายไว้ที่นี่

นี่คือรายละเอียดเพิ่มเติมเกี่ยวกับหัวข้อต่าง ๆ เหล่านี้:

PromisifyAll

ในโปรเจ็กต์ node.js ใด ๆ ฉันก็ใช้ Bluebird ได้ทุกที่เพราะฉันใช้.promisifyAll()บ่อยๆในโมดูล node.js มาตรฐานเช่นfsโมดูล

Node.js ไม่ได้จัดเตรียมอินเทอร์เฟซแบบสัญญากับโมดูลในตัวที่ทำ async IO เหมือนfs โมดูล ดังนั้นหากคุณต้องการใช้สัญญากับส่วนต่อประสานเหล่านั้นคุณจะเหลือรหัสมือเป็นตัวห่อสัญญารอบ ๆ แต่ละฟังก์ชั่นโมดูลที่คุณใช้หรือรับไลบรารีที่สามารถทำสิ่งนั้นเพื่อคุณหรือไม่ใช้สัญญา

Bluebird's Promise.promisify()และPromise.promisifyAll()จัดเตรียมการห่อ node.js ที่เรียกแบบแผน async APIs เพื่อคืนสัญญา มันมีประโยชน์มากและประหยัดเวลา ฉันจะใช้มันตลอดเวลา.

นี่คือตัวอย่างของวิธีการทำงาน:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

ทางเลือกอื่นคือสร้าง wrapper สัญญาของคุณเองสำหรับแต่ละfsAPI ที่คุณต้องการใช้:

const fs = require('fs');

function readFileAsync(file, options) {
    return new Promise(function(resolve, reject) {
        fs.readFile(file, options, function(err, data) {
            if (err) {
                reject(err);
            } else {
                 resolve(data);
            }
        });
    });
}

readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

และคุณต้องทำสิ่งนี้ด้วยตนเองสำหรับแต่ละฟังก์ชั่น API ที่คุณต้องการใช้ ชัดเจนว่าไม่เข้าท่า มันเป็นรหัสสำเร็จรูป คุณอาจได้รับยูทิลิตี้ที่เหมาะกับคุณ ครามPromise.promisify()และPromise.promisifyAll()เป็นยูทิลิตี้ดังกล่าว

คุณสมบัติที่มีประโยชน์อื่น ๆ

ต่อไปนี้เป็นคุณสมบัติบางอย่างของ Bluebird ที่ฉันพบว่ามีประโยชน์โดยเฉพาะ (มีตัวอย่างรหัสคู่ด้านล่างเกี่ยวกับวิธีการบันทึกรหัสหรือการพัฒนาความเร็ว)

Promise.promisify()
Promise.promisifyAll()
Promise.map()
Promise.reduce()
Promise.mapSeries()
Promise.delay()

นอกเหนือจากฟังก์ชั่นที่มีประโยชน์แล้วPromise.map()ยังรองรับตัวเลือกการทำงานพร้อมกันที่ช่วยให้คุณระบุจำนวนการดำเนินการที่ควรได้รับอนุญาตให้ทำงานในเวลาเดียวกันซึ่งมีประโยชน์อย่างยิ่งเมื่อคุณมีหลายอย่างที่ต้องทำ ทรัพยากร.

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


polyfill

ในโครงการเบราว์เซอร์เนื่องจากโดยทั่วไปคุณต้องการสนับสนุนเบราว์เซอร์บางตัวที่ไม่มีการสนับสนุนตามสัญญาคุณจึงจำเป็นต้องมีโพลีฟิลอยู่แล้ว หากคุณใช้ jQuery ด้วยบางครั้งคุณสามารถใช้สัญญาสนับสนุนที่สร้างไว้ใน jQuery (แม้ว่าจะไม่ได้มาตรฐานอย่างเจ็บปวดในบางวิธีอาจแก้ไขใน jQuery 3.0) แต่ถ้าโครงการนั้นเกี่ยวข้องกับกิจกรรม async ที่มีนัยสำคัญฉันพบ คุณสมบัติเพิ่มเติมใน Bluebird มีประโยชน์มาก


ได้เร็วขึ้น

นอกจากนี้ยังควรสังเกตว่าสัญญาของ Bluebird นั้นเร็วกว่าสัญญาที่สร้างไว้ใน V8 อย่างมีนัยสำคัญ ดูโพสต์นี้สำหรับการสนทนาเพิ่มเติมในหัวข้อนั้น


Node สิ่งที่ยิ่งใหญ่สิ่งที่ขาดหายไป

สิ่งที่จะทำให้ฉันพิจารณาว่าใช้ Bluebird น้อยลงในการพัฒนา node.js หาก node.js สร้างขึ้นในฟังก์ชั่น promisify เพื่อให้คุณสามารถทำสิ่งนี้:

const fs = requirep('fs');

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

หรือเพียงแค่เสนอวิธีการที่ได้รับการเสนอให้เป็นส่วนหนึ่งของโมดูลในตัว

ก่อนหน้านี้ฉันทำกับ Bluebird:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

ดูเหมือนแปลกเล็กน้อยที่จะมีการสนับสนุนสัญญา ES6 ที่สร้างไว้ใน node.js และไม่มีโมดูลในตัวที่ส่งคืนสัญญา สิ่งนี้จำเป็นต้องแยกออกใน node.js ก่อนหน้านี้ฉันใช้ Bluebird เพื่อแนะนำห้องสมุดทั้งหมด ดังนั้นจึงรู้สึกว่ามีการใช้งานสัญญาประมาณ 20% ใน node.js ในขณะนี้เนื่องจากไม่มีโมดูลในตัวให้คุณใช้สัญญากับพวกเขาโดยไม่ต้องห่อหุ้มด้วยตนเองก่อน


ตัวอย่าง

นี่คือตัวอย่างของ Promises vs. Bluebird และPromise.map()สำหรับการอ่านชุดของไฟล์ในแบบคู่ขนานและการแจ้งเตือนเมื่อทำกับข้อมูลทั้งหมด:

สัญญาธรรมดา

const files = ["file1.txt", "fileA.txt", "fileB.txt"];
const fs = require('fs');

// make promise version of fs.readFile()
function fsReadFileP(file, options) {
    return new Promise(function(resolve, reject) {
        fs.readFile(file, options, function(err, data) {
            if (err) return reject(err);
            resolve(data);
        });
    });
}


Promise.all(files.map(fsReadFileP)).then(function(results) {
    // files data in results Array
}, function(err) {
    // error here
});

ครามPromise.map()และPromise.promisifyAll()

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const files = ["file1.txt", "fileA.txt", "fileB.txt"];

Promise.map(files, fs.readFileAsync).then(function(results) {
    // files data in results Array
}, function(err) {
    // error here
});

นี่คือตัวอย่างของ Promises vs. Bluebird และPromise.map()เมื่ออ่านกลุ่มของ URL จากรีโมตโฮสต์ที่คุณสามารถอ่านได้ครั้งละไม่เกิน 4 รายการ แต่ต้องการเก็บคำขอจำนวนมากในแบบคู่ขนานตามที่ได้รับอนุญาต:

สัญญา JS ธรรมดา

const request = require('request');
const urls = [url1, url2, url3, url4, url5, ....];

// make promisified version of request.get()
function requestGetP(url) {
    return new Promise(function(resolve, reject) {
        request.get(url, function(err, data) {
            if (err) return reject(err);
            resolve(data);
        });
    });
}

function getURLs(urlArray, concurrentLimit) {
    var numInFlight = 0;
    var index = 0;
    var results = new Array(urlArray.length);
    return new Promise(function(resolve, reject) {
        function next() {
            // load more until concurrentLimit is reached or until we got to the last one
            while (numInFlight < concurrentLimit && index < urlArray.length) {
                (function(i) {
                    requestGetP(urlArray[index++]).then(function(data) {
                        --numInFlight;
                        results[i] = data;
                        next();
                    }, function(err) {
                        reject(err);
                    });
                    ++numInFlight;
                })(index);
            }
            // since we always call next() upon completion of a request, we can test here
            // to see if there was nothing left to do or finish
            if (numInFlight === 0 && index === urlArray.length) {
                resolve(results);
            }
        }
        next();
    });
}

สัญญาของ Bluebird

const Promise = require('bluebird');
const request = Promise.promisifyAll(require('request'));
const urls = [url1, url2, url3, url4, url5, ....];

Promise.map(urls, request.getAsync, {concurrency: 4}).then(function(results) {
    // urls fetched in order in results Array
}, function(err) {
    // error here
});

แม้ว่าจะไม่ได้มาตรฐานอย่างเจ็บปวดในบางวิธี - พวกเขาอ้างว่าพวกเขาเป็น "สัญญา / A + เข้ากันได้" ตอนนี้ :) - blog.jquery.com/2016/01/14/jquery-3-0-beta-released
thefourtheye

1
@thefourtheye - ใช่ฉันรู้ว่าพวกเขาได้ทำงานร่วมกับ Promise / A + ใน 3.0 แต่นั่นยังอยู่ในช่วงเบต้า ถ้ามันเป็นไปตามสัญญา (ตั้งใจจะเล่น) มันอาจจะทำให้บางส่วนของเหตุผลที่ใช้ห้องสมุดสัญญาภายนอกในเบราว์เซอร์ JS ถ้าคุณใช้ jQuery อยู่แล้ว มันจะยังไม่มีคุณสมบัติที่มีประโยชน์ทั้งหมดที่ Bluebird ทำและฉันจะประหลาดใจอย่างมากถ้ามันใช้งานได้ถึงประสิทธิภาพของ Bluebird ดังนั้นยังมีที่ว่างสำหรับ Bluebird ควบคู่ไปกับ jQuery ในอนาคตในบางกรณี ในกรณีใด ๆ คำถามของ OP ดูเหมือนจะเกี่ยวกับ node.js เป็นส่วนใหญ่
jfriend00

1
มีการพิมพ์ผิดเล็ก ๆ น้อย ๆ return new Promise(function(resolve, rejct)ในโค้ดตัวอย่างสุดท้ายคือ: ควรเป็น:reject
Sebastian Muszyński

7
util.promisifyตอนนี้Node.js มีอยู่แล้วแม้ว่าจะไม่promisifyAllเทียบเท่าโดยตรง
nyuszika7h

1
@Aurast - ใช่ v11 ดูแลfsแต่ก็ยังมีเหตุผลอื่นที่จะใช้ Bluebird (ที่ชื่นชอบโดยเฉพาะของฉันคือconcurrencyตัวเลือกในPromise.map()) เพื่อป้องกันไม่ให้บริการเป้าหมายที่คุณต้องการที่จะทำให้คำขอขนาน นอกจากนี้ยังมีอินเทอร์เฟซที่ไม่ได้รับการรับรองอื่น ๆ อีกมากมายที่จะใช้ promisifyAll ของ Bluebird ด้วย แต่ช้าเหตุผลที่จะคว้า Bluebird ทันทีในทุก ๆ โปรเจ็กต์ใหม่กำลังจะจางหายไปเนื่องจาก node.js ทำให้การสนับสนุนสัญญาในตัว
jfriend00
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.