Viết code Node JS bất đồng bộ với Promises

Bạn đã bao giờ tự hỏi làm thế nào JavaScript không đồng bộ? Trong thế giới nhanh này, các ứng dụng phức tạp đang được tạo ra mỗi ngày. Để quản lý sự phức tạp đó, ta cần những công cụ tốt để xác định và sửa đổi mã. Những lời hứa là những cấu trúc được giới thiệu để làm giảm sự phức tạp của mã JavaScript không đồng bộ.

Promises là gì ?

Promise là một proxy cho một giá trị không nhất thiết được biết đến khi lời hứa được tạo ra. Nó cho phép bạn kết hợp các trình điều khiển với giá trị thành công cuối cùng của sự kiện không đồng bộ hoặc lý do thất bại. Điều này cho phép các phương thức không đồng bộ trả lại các giá trị như các phương pháp đồng bộ: thay vì giá trị cuối cùng, phương thức không đồng bộ trả về một lời hứa cho giá trị tại một số điểm trong tương lai.

Nói một cách đơn giản "Lời hứa là một từ dùng cho hành động, bên kia hứa hẹn có thể hoàn thành nó hoặc từ chối nó". Trong trường hợp hoàn thành, lời hứa được giải quyết, và trong một trường hợp khác, nó bị từ chối. Chúng ta có thể tạo ra một lời hứa trong JavaScript và sử dụng nó như là một sự kiện sắp tới để mô tả vài hành động. Lời hứa là loại mẫu thiết kế để loại bỏ việc sử dụng các cuộc gọi lại không ngờ vực.

Tạo một Promises

Chúng ta có thể tạo ra một lời hứa trong chương trình Node JS bằng cách sử dụng hàm tạo mới. Đối với tất cả các ví dụ tôi sử dụng Node v6.5.0. Bạn nên cài đặt Node JS trên máy của bạn trước khi bắt đầu với hướng dẫn này. Mặc dù Promises có thể được sử dụng trong các trình duyệt, bài viết này chủ yếu tập trung vào việc viết mã không đồng bộ trên Node.

var myPromise = new Promise(function(resolve, reject){
   ....
})

Vì vậy, myPromise là một loại Promise đối tượng cho phép chúng ta sử dụng nó cho sau này. Mọi người đều biết về Github API. Nếu không, nó là một REST API cung cấp bởi Github để tìm ra các chi tiết về Users, Respositories vv Chúng ta hãy lấy một API để làm ví dụ. Đó là người dùng API. Một cái gì đó như thế này:

https://api.github.com/users/narenaryan

Nếu bạn thực hiện yêu cầu HTTP GET cho URL này, bạn sẽ được trả lại JSON với tất cả các thống kê về bản thân mình như repos, người theo dõi, những người theo dõi, các ngôi sao vv

Để tạo yêu cầu HTTP từ ứng dụng Node, chúng ta hãy cài đặt một gói nhỏ làm cho mọi thứ trở nên rõ ràng.

sudo npm install request -g
var userDetails;
function initialize() {
    // Setting URL and headers for request
    var options = {
        url: 'https://api.github.com/users/narenaryan',
        headers: {
            'User-Agent': 'request'
        }
    };
    // Return new promise 
    return new Promise(function(resolve, reject) {
     // Do async job
        request.get(options, function(err, resp, body) {
            if (err) {
                reject(err);
            } else {
                resolve(JSON.parse(body));
            }
        })
    })
}

Bạn đang khởi tạo biến khai báo userData ở đâu? chức năng khởi tạo đang trả về một lời hứa (promises) thay vì thiết lập dữ liệu hoặc trả lại dữ liệu. Chúng ta cần có lời hứa (promises) và xử lý nó theo cách mà chúng ta có thể điền vào các biến và tiến hành chương trình của chúng ta từ đó. Bây giờ chúng ta tạo ra một hàm chính, nơi chúng ta có được Promise cho chức năng trên và đính kèm một chức năng gọi lại trong chức năng sau đó.

var request = require("request");
var userDetails;

function initialize() {
    // Setting URL and headers for request
    var options = {
        url: 'https://api.github.com/users/narenaryan',
        headers: {
            'User-Agent': 'request'
        }
    };
    // Return new promise 
    return new Promise(function(resolve, reject) {
    	// Do async job
        request.get(options, function(err, resp, body) {
            if (err) {
                reject(err);
            } else {
                resolve(JSON.parse(body));
            }
        })
    })

}

function main() {
    var initializePromise = initialize();
    initializePromise.then(function(result) {
        userDetails = result;
        console.log("Initialized user details");
        // Use user details from here
        console.log(userDetails)
    }, function(err) {
        console.log(err);
    })
}

main();

Đầu ra trông như thế này:

Initialized user details
{
 "login": "narenaryan",
 "id": 5425726,
 "avatar_url": "https://avatars3.githubusercontent.com/u/5425726?v=3",
 "gravatar_id": "",
 "url": "https://api.github.com/users/narenaryan",
 "html_url": "https://github.com/narenaryan",
 "followers_url": "https://api.github.com/users/narenaryan/followers",
 "following_url": "https://api.github.com/users/narenaryan/following{/other_user}",
 "gists_url": "https://api.github.com/users/narenaryan/gists{/gist_id}",
 "starred_url": "https://api.github.com/users/narenaryan/starred{/owner}{/repo}",
 "subscriptions_url": "https://api.github.com/users/narenaryan/subscriptions",
 "organizations_url": "https://api.github.com/users/narenaryan/orgs",
 "repos_url": "https://api.github.com/users/narenaryan/repos",
 "events_url": "https://api.github.com/users/narenaryan/events{/privacy}",
 "received_events_url": "https://api.github.com/users/narenaryan/received_events",
 "type": "User",
 "site_admin": false,
 "name": "Naren Arya",
 "company": "Citrix R&D India",
 "blog": "http://narenarya.in",
 "location": "Banglaore",
 "email": null,
 "hireable": true,
 "bio": "A Software Development Engineer with expertise in Python and JavaScript. Coding in Golang and Reading books are his hobbies .",
 "public_repos": 69,
 "public_gists": 41,
 "followers": 134,
 "following": 7,
 "created_at": "2013-09-10T09:01:57Z",
 "updated_at": "2017-04-24T04:39:04Z"
}

Giả sử bạn muốn thực hiện một hoạt động sau khi một lời hứa được hoàn thành sử dụng một method để chuyển đổi dữ liệu bạn thu được từ promises. Bạn cần phải trả lại gists + repos count của narenaryan trên github, chỉ cần thêm như thế này:

function main() {
    var initializePromise = initialize();
    initializePromise.then(function(result) {
        userDetails = result;
        console.log("Initialized user details");
        // Use user details from here
        return userDetails;
    }, function(err) {
        console.log(err);
    }).then(function(result) {
        // Print the code activity. Prints 110
        console.log(result.public_gists + result.public_repos);
    })
}

Bằng cách kết nối sau đó sẽ thực hiện một lời hứa rằng chúng ta có thể truyền dữ liệu tới các chức năng tiếp theo. Nếu bạn đang viết logic bằng cách khởi tạo dữ liệu và sau đó sử dụng nó trong nhiều chức năng, ví dụ trên có thể giúp bạn. Các thiết kế trên là tốt nhưng không phải là tốt nhất.

Chúng ta cũng có thể xếp hàng các hành động không đồng bộ bằng Promises. Một cái gì đó tương tự như mô hình Singleton có thể đạt được bằng cách sử dụng chúng.

Khi một giá trị được trả lại từ đó, tiếp theo sau đó có thể nhận được giá trị. Chúng ta cũng có thể trả lại một lời hứa từ đó để cho chức năng chuỗi tiếp theo có thể sử dụng nó để xây dựng logic riêng của nó.

var request = require("request");
var userDetails;

function getData(url) {
    // Setting URL and headers for request
    var options = {
        url: url,
        headers: {
            'User-Agent': 'request'
        }
    };
    // Return new promise 
    return new Promise(function(resolve, reject) {
        // Do async job
        request.get(options, function(err, resp, body) {
            if (err) {
                reject(err);
            } else {
                resolve(body);
            }
        })
    })
}

var errHandler = function(err) {
    console.log(err);
}

function main() {
    var userProfileURL = "https://api.github.com/users/narenaryan";
    var dataPromise = getData(userProfileURL);
    // Get user details after that get followers from URL
    dataPromise.then(JSON.parse, errHandler)
               .then(function(result) {
                    userDetails = result;
                    // Do one more async operation here
                    var anotherPromise = getData(userDetails.followers_url).then(JSON.parse);
                    return anotherPromise;
                }, errHandler)
                .then(function(data) {
                    console.log(data)
                }, errHandler);
}


main();

Đầu ra là danh sách chi tiết của những người theo dõi Github của tôi.

[ { login: 'kmvkrish',
    id: 10069490,
    avatar_url: 'https://avatars2.githubusercontent.com/u/10069490?v=3',
    gravatar_id: '',
    url: 'https://api.github.com/users/kmvkrish',
    html_url: 'https://github.com/kmvkrish',
..........
......
}]

Nếu bạn quan sát ở trên chúng ta sẽ trả lại một tính năng khác, nhưng trong phần tiếp theo chúng ta đang sử dụng dữ liệu như dữ liệu bình thường. Đoạn mã trên làm cho hai yêu cầu HTTP đến Github API nhưng cuối cùng nhận được dữ liệu chính xác và in nó vào bảng điều khiển.

Tạo một chuỗi các Promises

Chúng ta có thể thực hiện một loạt các lời hứa để làm việc theo một thứ tự cụ thể. Chúng ta có thể sử dụng hàm Promise.all để đưa ra một danh sách các lời hứa theo thứ tự nhất định và trả về một lời hứa khác mà chúng ta có thể sử dụng một phương thức để kết luận logic.

Hãy viết một chương trình mẫu sử dụng Promise.all. Chúng ta viết nó theo phong cách ES6.

var message = "";

promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        message += "my";
        resolve(message);
    }, 2000)
})

promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        message += " first";
        resolve(message);
    }, 2000)
})

promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        message += " promise";
        resolve(message);
    }, 2000)
})

var printResult = (results) => {console.log("Results = ", results, "message = ", message)}

function main() {
    // See the order of promises. Final result will be according to it
    Promise.all([promise1, promise2, promise3]).then(printResult);
    Promise.all([promise2, promise1, promise3]).then(printResult);
    Promise.all([promise3, promise2, promise1]).then(printResult);
    console.log("\"\"" + message);
}

main();

setTimeout được sử dụng để mô phỏng hoạt động chặn async. Chúng ta đang tạo ra ba lời hứa (promises) và nối thêm một chuỗi vào biến ban đầu được gọi là message. Chúng ta nên sử dụng Promise.all khi chúng ta không quan tâm đến thứ tự thực hiện nhưng cuối cùng message phải được chứa đầy nội dung mong muốn.

Đầu ra cho chương trình trên trông giống như:

results là kết quả của mỗi lời hứa trong danh sách. Dữ liệu đó đi qua chức năng printResult ở đây.

Đầu ra rõ ràng cho biết thông điệp cuối cùng đang được cập nhật đúng cách.

Lưu ý: Promise.all không thành công nếu bất kỳ một trong các Promise đã bị từ chối.