Lập trình Promise với BlueBird qua ví dụ - Phần 1
Lập trình Promise với BlueBird qua ví dụ - Phần 2
Lập trình Promise với BlueBird - Phần 3: Map
Lập trình Promise với BlueBird - Phần 4: then vs spread
Trong BlueBird, chúng ta có 2 lựa chọn là all và map. Trong bài viết này tôi so sánh giống và khác nhau giữa all và map:

Giống nhau giữa all và map

Cả hai đều xử lý tập các promise. Nếu tất cả các promise hoành thành thì hàm then tiếp theo sẽ xử lý mảng kết quả trả về, theo đúng thứ tự mảng ban đầu. Chỉ cần một promise thất bại (reject) là hàm catch sẽ được gọi.

Khác nhau giữa all và map

all nhận vào một mảng các promise. Còn map thì nhận vào một mảng bình thường, và có hàm callback duyệt từng phần tử mảng để trả về promise. Ngoài ra map còn có tham số tùy biến là concurrency để giới hạn tối đa có bao nhiêu promise có thể được hoàn thành (fullfill) tại cùng một thời điểm.

Vậy nếu bạn có trong tay mảng không phải promise thì buộc phải dùng map, ngược lại dùng all cho nhanh.

Ví dụ promise.all

Đọc vào 2 file, rồi cùng xuất ra màn hình.

var promise = require("bluebird");
var readFileAsync = promise.promisify(require('fs').readFile);

promise.all([readFileAsync('good.json'), readFileAsync('bad.json')]).
    then(function(files) {
        console.log(files[0].toString(), files[1].toString());
    }).catch(function(err){
        console.error('Cannot not read file ' + err.message);
    });

Tham số concurrency của promise.map

Ví dụ dưới đây sử dụng promise.map để tải về các ảnh từ trang unsplash

var fs = require('fs');
var promise = require("bluebird");
var request = require('request');
var photoLinks = [{link: 'https://unsplash.imgix.net/photo-1425235024244-b0e56d3cf907?fit=crop&fm=jpg&h=700&q=75&w=1050',
    name: 'dog.jpg'},
    {link: 'https://unsplash.imgix.net/reserve/NxxLccbqQdGtNc7xJ43e_ancestral-home.jpg?fit=crop&fm=jpg&h=600&q=75&w=1050',
        name: 'house.jpg'},
    {link: 'https://unsplash.imgix.net/photo-1423439793616-f2aa4356b37e?q=75&fm=jpg&s=3b42f9c018b2712544debf4d6a4998ff',
      name: 'car.jpg'},
    {link: 'https://unsplash.imgix.net/photo-1422513391413-ddd4f2ce3340?q=75&fm=jpg&s=282e5978de17d6cd2280888d16f06f04',
      name: 'nightstar.jpg'}
];

function getPhoto(photoLink){
    return new Promise(function(fulfill, reject) {
        request.get(photoLink.link)
            .on('error', function (err) {
                err.photo = photoLink.link;
                reject(err);
            })
            .pipe(fs.createWriteStream(photoLink.name)
                .on('finish', function () {
                    fulfill(photoLink.name);
                }).on('error', function (err) {
                    reject(err);
                })
        );
    });
}
console.time("getPhoto");
promise.map(photoLinks, function(item){
  return getPhoto(item);
}, {concurrency: 6}).then(function(result){
    console.log(result);
    console.timeEnd("getPhoto");
}).catch(function(err){
    console.log(err);
});

Nếu để concurrency = 6 thì có tối đa 6 promise cùng trả về, thì mất khoảng 4 giây tải tất cả các ảnh

[ 'dog.jpg', 'house.jpg', 'car.jpg', 'nightstar.jpg' ]
getPhoto: 4101ms

Nếu để concurrency = 1, thời gian hoàn thành việc tải về các photo sẽ lâu hơn, khoảng 10 giây.

[ 'dog.jpg', 'house.jpg', 'car.jpg', 'nightstar.jpg' ]
getPhoto: 10891ms

Làm thế nào để in ra kết quả từng tác vụ trong mảng?

Thay vì trả về luôn hàm return getPhoto(item); như ví dụ trên, thì hãy thêm then để xử lý từng tác vụ luôn

promise.map(photoLinks, function(photoLink) {
    return getPhoto(photoLink).then(function(result) {
        now(result);
        return result;
    });
}).then

Code đầy đủ ở đây 

var fs = require('fs');
var promise = require("bluebird");
var request = require('request');
var photoLinks = [{link: 'https://unsplash.imgix.net/photo-1425235024244-b0e56d3cf907?fit=crop&fm=jpg&h=700&q=75&w=1050',
    name: 'dog.jpg'},
    {link: 'https://unsplash.imgix.net/reserve/NxxLccbqQdGtNc7xJ43e_ancestral-home.jpg?fit=crop&fm=jpg&h=600&q=75&w=1050',
        name: 'house.jpg'},
    {link: 'https://unsplash.imgix.net/photo-1423439793616-f2aa4356b37e?q=75&fm=jpg&s=3b42f9c018b2712544debf4d6a4998ff',
      name: 'car.jpg'},
    {link: 'https://unsplash.imgix.net/photo-1422513391413-ddd4f2ce3340?q=75&fm=jpg&s=282e5978de17d6cd2280888d16f06f04',
      name: 'nightstar.jpg'}
];

function getPhoto(photoLink){
    return new Promise(function(fulfill, reject) {
        request.get(photoLink.link)
            .on('error', function (err) {
                err.photo = photoLink.link;
                reject(err);
            })
            .pipe(fs.createWriteStream(photoLink.name)
                .on('finish', function () {
                    fulfill(photoLink.name);
                }).on('error', function (err) {
                    reject(err);
                })
        );
    });
}

function now(txt) {
    console.log(new Date().toLocaleTimeString().replace(/T/, ' ').replace(/\..+/, '')+' '+txt);
}
console.time("getPhoto");
promise.map(photoLinks, function(photoLink) {
    return getPhoto(photoLink).then(function(result) {
        now(result);  //In ra thời điểm và kết quả khi từng tác vụ thành công. Thứ tự sẽ khác với thứ tự các phần tử mảng
        return result;
    });
}).then(function(result) {  //Sau khi tất cả các tác vụ thành công. Thứ tự giống với mảng đầu vào
    console.log('done all: ', result);
    console.timeEnd("getPhoto");
}).catch(function(err){
    console.log('Error:\n', err.message);
});

Kết quả in ra, cho thấy thứ tự hoàn thành của các tác vụ download là khác nhau, nhưng hàm then sau promise.map sẽ đồng bộ và xắp xếp kết quả theo đúng thứ tự mảng đầu vào. Cả promise.map và promise.all đều giống nhau ở điểm này.

20:29:24 house.jpg
20:29:25 dog.jpg
20:29:27 nightstar.jpg
20:29:31 car.jpg
done all:  [ 'dog.jpg', 'house.jpg', 'car.jpg', 'nightstar.jpg' ]
getPhoto: 9622ms