Thứ ba, 15/09/2020 | 00:00 GMT+7

Lời hứa của JavaScript dành cho người giả


Javascript Promise không khó. Tuy nhiên, nhiều người cảm thấy hơi khó hiểu khi bắt đầu. Vì vậy, tôi muốn viết ra theo cách tôi hiểu về những lời hứa, một cách giả tạo.

Hiểu lời hứa

Một lời hứa trong ngắn hạn:

“Hãy tưởng tượng bạn là một đứa trẻ . Mẹ bạn hứa với bạn rằng bà ấy sẽ nhận được điện thoại mới cho bạn vào tuần tới. "

Bạn không biết liệu mình có nhận được chiếc điện thoại đó cho đến tuần sau hay không. Mẹ bạn thực sự có thể mua cho bạn một chiếc điện thoại hoàn toàn mới, hoặc không , vì bà ấy không vui :(.

Đó là một lời hứa . Một lời hứa có 3 trạng thái. Họ đang:

  1. Đang chờ xử lý: Bạn không biết mình có nhận được điện thoại đó không
  2. Hoàn thành: Mẹ rất vui , mẹ mua cho bạn một chiếc điện thoại mới tinh
  3. Bị từ chối: Mẹ không vui , mẹ không mua điện thoại cho bạn

Tạo một lời hứa

Hãy chuyển nó thành JavaScript.

// ES5: Part 1

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

Bản thân mã khá biểu cảm.

Dưới đây là cách một cú pháp lời hứa trông giống như bình thường:

// promise syntax look like this
new Promise(function (resolve, reject) { ... } );

Thực hiện lời hứa

Bây giờ ta đã có lời hứa, hãy tiêu thụ nó.

// ES5: Part 2

var willIGetNewPhone = ... // continue from part 1

// call our promise
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // yay, you got a new phone
            console.log(fulfilled);
             // output: { brand: 'Samsung', color: 'black' }
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
             // output: 'mom is not happy'
        });
};

askMom();

Hãy chạy ví dụ và xem kết quả!

Demo: https://jsbin.com/nifocu/1/edit?js,console

Kết quả

Chuỗi lời hứa

Lời hứa là có thể thực hiện được.

Giả sử bạn, đứa trẻ, hứa với bạn của bạn rằng bạn sẽ cho họ xem chiếc điện thoại mới khi mẹ bạn mua cho bạn một chiếc.

Đó là một lời hứa khác. Hãy viết nó!

// ES5

// 2nd promise
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

Ghi chú: Ta có thể viết tắt đoạn mã trên bằng cách viết như sau:

// shorten it

// 2nd promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

Hãy xâu chuỗi những lời hứa. Bạn, đứa trẻ chỉ có thể bắt đầu lời hứa showOff sau lời hứa willIGetNewPhone .

// call our promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chain it here
    .then(function (fulfilled) {
            console.log(fulfilled);
         // output: 'Hey friend, I have a new black Samsung phone.'
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
         // output: 'mom is not happy'
        });
};

Đó là cách dễ dàng để xâu chuỗi lời hứa.

Hứa hẹn không đồng bộ

Lời hứa không đồng bộ. Hãy ghi lại một tin nhắn trước và sau khi ta gọi lời hứa.

// call our promise
var askMom = function () {
    console.log('before asking Mom'); // log before
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('after asking mom'); // log after
}

Trình tự của sản lượng mong đợi là gì? Có thể bạn mong đợi:

1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom

Tuy nhiên, trình tự kết quả thực tế là:

1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.

Không tin tôi? Hãy tự mình thử: https://jsbin.com/vepateq/edit?js,console !

Đầu ra

Tại sao? Vì cuộc đời (hay JS) không đợi một người đàn ông nào cả.

Bạn, đứa trẻ, sẽ không ngừng chơi trong khi chờ đợi lời hứa của mẹ (chiếc điện thoại mới). Không bạn? Đó là thứ mà ta gọi là không đồng bộ , mã sẽ chạy mà không bị chặn hoặc chờ kết quả. Bất cứ điều gì cần chờ đợi để thực hiện lời hứa, bạn đưa nó vào .then .

Đây là ví dụ đầy đủ trong ES5.

// ES5: Full example

var isMomHappy = true;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

// 2nd promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

// call our promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chain it here
    .then(function (fulfilled) {
            console.log(fulfilled);
            // output: 'Hey friend, I have a new black Samsung phone.'
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
            // output: 'mom is not happy'
        });
};

askMom();

Những hứa hẹn trong ES5, ES6 / 2015, ES7 / Next

ES5 - Đa số các trình duyệt

Mã demo có thể hoạt động trong môi trường ES5 (tất cả các trình duyệt chính + NodeJ) nếu bạn bao gồm thư viện lời hứa Bluebird . Đó là bởi vì ES5 không hỗ trợ các hứa hẹn ra khỏi hộp. Một thư viện lời hứa nổi tiếng khác là Q của Kris Kowal.

ES6 / ES2015 - Trình duyệt hiện đại, NodeJs v6

Mã demo hoạt động hiệu quả vì ES6 hỗ trợ các hứa hẹn nguyên bản. Ngoài ra, với các hàm ES6, ta có thể đơn giản hóa đoạn mã hơn nữa với fat arrow = > và sử dụng constlet .

Đây là ví dụ đầy đủ trong mã ES6:

//_ ES6: Full example_

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // fat arrow
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
const showOff = function (phone) {
    const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
    return Promise.resolve(message);
};

// call our promise
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // fat arrow
        .catch(error => console.log(error.message)); // fat arrow
};

askMom();

Lưu ý tất cả các var được thay thế bằng const . Tất cả các function(resolve, reject) đã được đơn giản hóa thành (resolve, reject) => . Có một số lợi ích đi kèm với những thay đổi này. Đọc thêm về: -

ES7 - Async Await làm cho cú pháp trông đẹp hơn

ES7 giới thiệu cú pháp asyncawait . Nó làm cho không đồng bộ cú pháp nhìn đẹp hơn và dễ hiểu hơn, nếu không có sự .then.catch .

Viết lại ví dụ của ta với cú pháp ES7.

// ES7: Full example
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

// call our promise in ES7 async await style
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

// async await it here too
(async () => {
    await askMom();
})();

Tại sao phải hứa và khi nào sử dụng chúng?

Tại sao ta cần những lời hứa? Thế giới trông như thế nào trước khi hứa? Trước khi trả lời những câu hỏi này, hãy quay lại vấn đề cơ bản.

Chức năng bình thường và chức năng không đồng bộ

Hãy xem xét hai ví dụ này, cả hai ví dụ này thực hiện phép cộng hai số, một phép cộng bằng cách sử dụng hàm bình thường, ví dụ kia thêm từ xa.

Chức năng bình thường để thêm hai số

// add two numbers normally

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // you get result = 3 immediately
Hàm Async để thêm hai số
// add two numbers remotely

// get the result by calling an API
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// you get result  = "undefined"

Nếu bạn thêm các số với chức năng bình thường, bạn sẽ nhận được kết quả ngay lập tức. Tuy nhiên, khi gọi từ xa để lấy kết quả, bạn cần chờ đợi, không thể nhận ngay kết quả.

Hay nói theo cách này, bạn không biết mình có nhận được kết quả hay không vì server có thể bị trục trặc, phản hồi chậm,… Bạn không muốn toàn bộ quá trình của bạn bị chặn trong khi chờ kết quả.

Gọi API, download file , đọc file là một trong số các thao tác không đồng bộ thông thường mà bạn sẽ thực hiện.

Thế giới trước những lời hứa: Gọi lại

Ta có phải sử dụng lời hứa cho cuộc gọi không đồng bộ không? Không. Trước Promise, ta sử dụng callback. Gọi lại chỉ là một hàm bạn gọi khi nhận được kết quả trả về. Hãy sửa đổi ví dụ trước để chấp nhận một cuộc gọi lại.

// add two numbers remotely
// get the result by calling an API

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback
    const result = success; // you get result = 3 here
});

Cú pháp có vẻ ổn, tại sao ta cần lời hứa?

Điều gì sẽ xảy ra nếu bạn muốn thực hiện hành động không đồng bộ tiếp theo?

Giả sử, thay vì chỉ cộng các số một lần, ta muốn cộng 3 lần. Trong một chức năng bình thường, ta thực hiện điều này: -

// add two numbers normally

let resultA, resultB, resultC;

 function add (num1, num2) {
    return num1 + num2;
}

resultA = add(1, 2); // you get resultA = 3 immediately
resultB = add(resultA, 3); // you get resultB = 6 immediately
resultC = add(resultB, 4); // you get resultC = 10 immediately

console.log('total' + resultC);
console.log(resultA, resultB, resultC);

Nó trông như thế nào với các cuộc gọi lại?

// add two numbers remotely
// get the result by calling an API

let resultA, resultB, resultC;

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
    // https://api.jquery.com/jQuery.getJSON/
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback 1
    resultA = success; // you get result = 3 here

    addAsync(resultA, 3, success => {
        // callback 2
        resultB = success; // you get result = 6 here

        addAsync(resultB, 4, success => {
            // callback 3
            resultC = success; // you get result = 10 here

            console.log('total' + resultC);
            console.log(resultA, resultB, resultC);
        });
    });
});

Demo: https://jsbin.com/barimo/edit?html,js,console

Cú pháp kém thân thiện với user . Nói một cách dễ hiểu hơn, Nó trông giống như một kim tự tháp, nhưng mọi người thường gọi đây là “địa ngục gọi lại”, bởi vì lệnh gọi lại lồng vào một lệnh gọi lại khác. Hãy tưởng tượng bạn có 10 lần gọi lại, mã của bạn sẽ lồng vào nhau 10 lần!

Thoát khỏi địa ngục gọi lại

Hứa hẹn sẽ giải cứu. Hãy xem version hứa hẹn của cùng một ví dụ.

// add two numbers remotely using observable

let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // use ES6 fetch API, which return a promise
    // What is .json()? https://developer.mozilla.org/en-US/docs/Web/API/Body/json
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(1, 2)
    .then(success => {
        resultA = success;
        return resultA;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        resultB = success;
        return resultB;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        resultC = success;
        return resultC;
    })
    .then(success => {
        console.log('total: ' + success)
        console.log(resultA, resultB, resultC)
    });

Demo: https://jsbin.com/qafane/edit?js,console

Với những lời hứa, ta san bằng lệnh gọi lại bằng .then . Theo một cách nào đó, nó trông gọn gàng hơn vì không có lồng gọi lại. Tất nhiên, với ES7 async cú pháp, ta thậm chí có thể nâng cao hơn nữa ví dụ này, nhưng tôi lại đó cho bạn. :)

Đứa trẻ mới trên khối: Có thể quan sát

Trước khi bạn ổn định với những lời hứa, có một điều gì đó sắp xảy ra giúp bạn dễ dàng xử lý dữ liệu không đồng bộ được gọi là Observables .

Có thể quan sát là các stream sự kiện lười biếng có thể tạo ra không hoặc nhiều sự kiện và có thể kết thúc hoặc có thể không. nguồn

Một số điểm khác biệt chính giữa lời hứa và có thể quan sát được là:

Đừng sợ, hãy xem cùng một bản demo được viết bằng Observables. Trong ví dụ này, tôi đang sử dụng RxJS cho các đối tượng có thể quan sát.

let Observable = Rx.Observable;
let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // use ES6 fetch API, which return a promise
    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json());

    return Observable.fromPromise(promise);
}

addAsync(1,2)
  .do(x => resultA = x)
  .flatMap(x => addAsync(x, 3))
  .do(x => resultB = x)
  .flatMap(x => addAsync(x, 4))
  .do(x => resultC = x)
  .subscribe(x => {
    console.log('total: ' + x)
    console.log(resultA, resultB, resultC)
  });

Demo: https://jsbin.com/dosaviwalu/edit?js,console

Ghi chú:

Những người quan sát có thể dễ dàng thực hiện những việc thú vị hơn. Ví dụ: delay chức năng thêm 3 seconds chỉ với một dòng mã hoặc thử lại để bạn có thể thử lại một cuộc gọi một số lần nhất định.

...

addAsync(1,2)
  .delay(3000) // delay 3 seconds
  .do(x => resultA = x)
  ...

Vâng, hãy nói về Observables trong bài đăng sau. (Bạn cũng có thể đọc một trong những bài đăng RxJ của tôi ở đây !)

Tóm lược

Làm quen với các cuộc gọi lại và lời hứa. Hiểu chúng và sử dụng chúng. Đừng lo lắng về Observables, chỉ được nêu ra. Cả ba yếu tố này có thể ảnh hưởng đến sự phát triển của bạn tùy thuộc vào tình huống.

Đó là nó. Hy vọng rằng bài viết này làm trơn tru con đường của bạn để chế ngự các hứa hẹn JavaScript. Chúc bạn viết mã vui vẻ!


Tags:

Các tin liên quan

Sao chép các đối tượng trong JavaScript
2020-09-15
Cách sử dụng API tìm nạp JavaScript để lấy dữ liệu
2020-09-15
Cách mã hóa và giải mã chuỗi với Base64 trong JavaScript
2020-09-15
Toán tử đơn nguyên JavaScript: Đơn giản và hữu ích
2020-09-15
Hiểu Hoisting trong JavaScript
2020-09-15
5 Mẹo để Viết Điều kiện Tốt hơn trong JavaScript
2020-09-15
Hiểu Vòng lặp sự kiện, Gọi lại, Hứa hẹn và Không đồng bộ / Chờ đợi trong JavaScript
2020-09-10
Bốn phương pháp để tìm kiếm thông qua các mảng trong JavaScript
2020-09-09
Sử dụng phương thức Array.find trong JavaScript
2020-09-09
split () Phương thức chuỗi trong JavaScript
2020-09-09