Câu ngạn ngữ cũ nói rằng bạn nên chọn công cụ phù hợp cho công việc. ES6 hứa hẹn cung cấp những điều cơ bản. Nếu tất cả những gì bạn muốn hoặc cần là những điều cơ bản, thì điều đó nên / có thể làm việc tốt cho bạn. Nhưng, có nhiều công cụ trong thùng công cụ hơn là những điều cơ bản và có những tình huống mà những công cụ bổ sung đó rất hữu ích. Và, tôi cho rằng các lời hứa ES6 thậm chí còn thiếu một số điều cơ bản như việc hứa hẹn rất hữu ích trong hầu hết mọi dự án của node.js.
Tôi quen thuộc nhất với thư viện lời hứa Bluebird vì vậy tôi sẽ nói hầu hết từ kinh nghiệm của tôi với thư viện đó.
Vì vậy, đây là 6 lý do hàng đầu của tôi để sử dụng thư viện Promise có khả năng hơn
Các giao diện không đồng bộ không được quảng cáo - .promisify()
và .promisifyAll()
cực kỳ hữu ích để xử lý tất cả các giao diện không đồng bộ vẫn yêu cầu gọi lại đơn giản và không trả lại lời hứa - một dòng mã tạo ra phiên bản được hứa hẹn của toàn bộ giao diện.
Nhanh hơn - Bluebird nhanh hơn đáng kể so với lời hứa bản địa trong hầu hết các môi trường.
Trình tự lặp lại mảng async - Promise.mapSeries()
hoặc Promise.reduce()
cho phép bạn lặp qua một mảng, gọi một thao tác async trên mỗi phần tử, nhưng tuần tự các hoạt động async để chúng xảy ra lần lượt, không phải tất cả cùng một lúc. Bạn có thể làm điều này bởi vì máy chủ đích yêu cầu hoặc vì bạn cần truyền một kết quả cho lần tiếp theo.
Polyfill - Nếu bạn muốn sử dụng lời hứa trong các phiên bản cũ hơn của ứng dụng khách trình duyệt, dù sao bạn cũng sẽ cần một polyfill. Cũng có thể có được một polyfill có khả năng. Vì node.js có các lời hứa ES6, bạn không cần polyfill trong node.js, nhưng bạn có thể trong trình duyệt. Nếu bạn đang mã hóa cả máy chủ và máy khách node.js, có thể rất hữu ích khi có cùng thư viện lời hứa và các tính năng trong cả hai (dễ chia sẻ mã, chuyển đổi ngữ cảnh giữa các môi trường, sử dụng các kỹ thuật mã hóa phổ biến cho mã async, v.v. .).
Các tính năng hữu ích khác - Bluebird có Promise.map()
, Promise.some()
, Promise.any()
, Promise.filter()
, Promise.each()
và Promise.props()
tất cả trong số đó là thỉnh thoảng tiện dụng. Mặc dù các hoạt động này có thể được thực hiện với các lời hứa ES6 và mã bổ sung, Bluebird đi kèm với các hoạt động này đã được xây dựng sẵn và thử nghiệm trước để sử dụng chúng đơn giản hơn và ít mã hơn.
Được xây dựng trong Cảnh báo và Dấu vết ngăn xếp đầy đủ - Bluebird có một số cảnh báo được xây dựng để cảnh báo bạn về các vấn đề có thể là mã sai hoặc lỗi. Ví dụ: nếu bạn gọi một hàm tạo ra một lời hứa mới bên trong .then()
trình xử lý mà không trả lại lời hứa đó (để liên kết nó với chuỗi lời hứa hiện tại), thì trong hầu hết các trường hợp, đó là một lỗi vô tình và Bluebird sẽ đưa ra cảnh báo cho bạn về điều đó hiệu ứng. Các cảnh báo Bluebird tích hợp khác được mô tả ở đây .
Dưới đây là một số chi tiết về các chủ đề khác nhau:
Hứa hẹn
Trong bất kỳ dự án node.js nào, tôi ngay lập tức sử dụng Bluebird ở mọi nơi vì tôi sử dụng .promisifyAll()
rất nhiều trên các mô-đun node.js tiêu chuẩn như fs
mô-đun.
Bản thân Node.js không cung cấp giao diện hứa hẹn cho các mô-đun tích hợp hoạt động không đồng bộ IO như fs
mô-đun. Vì vậy, nếu bạn muốn sử dụng các lời hứa với các giao diện đó, bạn sẽ để lại mã cho trình bao bọc lời hứa xung quanh từng chức năng mô-đun bạn sử dụng hoặc nhận thư viện có thể thực hiện điều đó cho bạn hoặc không sử dụng lời hứa.
Bluebird Promise.promisify()
và Promise.promisifyAll()
cung cấp gói tự động các API async quy ước gọi node.js để trả lại lời hứa. Nó cực kỳ hữu ích và tiết kiệm thời gian. Tôi sử dụng nó mọi lúc.
Đây là một ví dụ về cách thức hoạt động:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Cách khác là tạo thủ công trình bao bọc lời hứa của riêng bạn cho mỗi fs
API bạn muốn sử dụng:
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
});
Và, bạn phải tự làm điều này cho từng chức năng API bạn muốn sử dụng. Điều này rõ ràng không có ý nghĩa. Đó là mã soạn sẵn. Bạn cũng có thể có được một tiện ích làm việc này cho bạn. Bluebird Promise.promisify()
và Promise.promisifyAll()
là một tiện ích như vậy.
Các tính năng hữu ích khác
Dưới đây là một số tính năng của Bluebird mà tôi đặc biệt thấy hữu ích (có một vài ví dụ mã bên dưới về cách chúng có thể lưu mã hoặc tăng tốc độ phát triển):
Promise.promisify()
Promise.promisifyAll()
Promise.map()
Promise.reduce()
Promise.mapSeries()
Promise.delay()
Ngoài chức năng hữu ích của nó, Promise.map()
cũng hỗ trợ tùy chọn đồng thời cho phép bạn chỉ định số lượng hoạt động nên được phép chạy cùng lúc, điều này đặc biệt hữu ích khi bạn có nhiều việc phải làm, nhưng không thể lấn át một số hoạt động bên ngoài nguồn.
Một số trong số này có thể được gọi là độc lập và được sử dụng cho một lời hứa mà chính nó giải quyết thành một lần lặp có thể tiết kiệm rất nhiều mã.
Polyfill
Trong một dự án trình duyệt, vì bạn thường muốn vẫn hỗ trợ một số trình duyệt không có hỗ trợ Promise, cuối cùng bạn vẫn cần một polyfill. Nếu bạn cũng đang sử dụng jQuery, đôi khi bạn có thể chỉ sử dụng hỗ trợ lời hứa được tích hợp trong jQuery (mặc dù nó không chuẩn theo một số cách, có thể đã được sửa trong jQuery 3.0), nhưng nếu dự án liên quan đến bất kỳ hoạt động không đồng bộ hóa nào, tôi thấy các tính năng mở rộng trong Bluebird rất hữu ích.
Nhanh hơn
Cũng đáng chú ý rằng những lời hứa của Bluebird dường như nhanh hơn đáng kể so với những lời hứa được tích hợp trong động cơ V8. Xem bài đăng này để thảo luận thêm về chủ đề đó.
Một Node.js lớn đang thiếu
Điều khiến tôi cân nhắc sử dụng Bluebird ít hơn trong phát triển node.js sẽ là nếu node.js được xây dựng trong một hàm hứa hẹn để bạn có thể làm một cái gì đó như thế này:
const fs = requirep('fs');
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Hoặc chỉ cung cấp các phương thức đã được hứa hẹn như là một phần của các mô-đun tích hợp.
Cho đến lúc đó, tôi làm điều này với Bluebird:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Có vẻ hơi kỳ quặc khi có hỗ trợ lời hứa ES6 được tích hợp vào node.js và không có bất kỳ mô đun nào tích hợp trả lại lời hứa. Điều này cần được sắp xếp ra trong node.js. Cho đến lúc đó, tôi sử dụng Bluebird để quảng bá toàn bộ thư viện. Vì vậy, có vẻ như các lời hứa hiện được thực hiện khoảng 20% trong tệp node.js vì không có mô-đun tích hợp nào cho phép bạn sử dụng lời hứa với chúng mà không cần gói thủ công trước.
Ví dụ
Đây là một ví dụ về Promise đơn giản so với lời hứa của Bluebird và Promise.map()
để đọc một tập hợp các tệp song song và thông báo khi thực hiện với tất cả dữ liệu:
Lời hứa đồng bằng
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
});
Bluebird Promise.map()
và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
});
Đây là một ví dụ về Lời hứa đơn giản so với lời hứa của Bluebird và Promise.map()
khi đọc một loạt URL từ máy chủ từ xa, nơi bạn có thể đọc nhiều nhất 4 lần, nhưng muốn giữ song song nhiều yêu cầu như được phép:
Lời hứa đồng bằng
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();
});
}
Lời hứa của 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
});