了解 JavaScript 承诺

介绍

Javascript Promises可能很難理解,因此,我想寫下我理解承諾的方式。

理解承诺

一个短暂的承诺:

`想象一下你是一个孩子,你的妈妈答应你,她下周会给你一个手机。

你不知道你是否会得到那部手机直到下周,你的妈妈真的可以给你买一部全新的手机,或者她不会。

这是一个承诺,一个承诺有三个状态,它们是:

  1. 等待:你不知道你是否会得到那个电话
  2. 完成:妈妈是 happy,她给你买了一个全新的电话
  3. 拒绝:妈妈是 unhappy,她不给你买一个电话

创造一个承诺

让我们把它转换为 JavaScript。

 1// ES5: Part 1
 2
 3var isMomHappy = false;
 4
 5// Promise
 6var willIGetNewPhone = new Promise(
 7    function (resolve, reject) {
 8        if (isMomHappy) {
 9            var phone = {
10                brand: 'Samsung',
11                color: 'black'
12            };
13            resolve(phone); // fulfilled
14        } else {
15            var reason = new Error('mom is not happy');
16            reject(reason); // reject
17        }
18
19    }
20);

代码本身是相当表达性的。

下面是承诺语法的正常外观:

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

消费承诺

现在我们有了诺言,让我们消耗它:

 1// ES5: Part 2
 2
 3var willIGetNewPhone = ... // continue from part 1
 4
 5// call our promise
 6var askMom = function () {
 7    willIGetNewPhone
 8        .then(function (fulfilled) {
 9            // yay, you got a new phone
10            console.log(fulfilled);
11             // output: { brand: 'Samsung', color: 'black' }
12        })
13        .catch(function (error) {
14            // oops, mom didn't buy it
15            console.log(error.message);
16             // output: 'mom is not happy'
17        });
18};
19
20askMom();

让我们举个例子,看看结果!

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

Result

链接承诺

承诺是可靠的。

让我们告诉你,孩子,答应你的朋友,当你妈妈给你买一台新手机时,你会向他们展示新手机。

这是另一个承诺!让我们写下来!

 1// ES5
 2
 3// 2nd promise
 4var showOff = function (phone) {
 5    return new Promise(
 6        function (resolve, reject) {
 7            var message = 'Hey friend, I have a new ' +
 8                phone.color + ' ' + phone.brand + ' phone';
 9
10            resolve(message);
11        }
12    );
13};

注:我们可以通过写下面的方式缩短上述代码:

1// shorten it
2
3// 2nd promise
4var showOff = function (phone) {
5    var message = 'Hey friend, I have a new ' +
6                phone.color + ' ' + phone.brand + ' phone';
7
8    return Promise.resolve(message);
9};

你,孩子,只能在willIGetNewPhone的承诺之后开始showOff的承诺。

 1// call our promise
 2var askMom = function () {
 3    willIGetNewPhone
 4    .then(showOff) // chain it here
 5    .then(function (fulfilled) {
 6            console.log(fulfilled);
 7         // output: 'Hey friend, I have a new black Samsung phone.'
 8        })
 9        .catch(function (error) {
10            // oops, mom don't buy it
11            console.log(error.message);
12         // output: 'mom is not happy'
13        });
14};

这就是你可以链接承诺的方式。

承诺是同步的

承诺是同步的. 让我们在我们呼叫承诺之前和之后记录一个消息。

 1// call our promise
 2var askMom = function () {
 3    console.log('before asking Mom'); // log before
 4    willIGetNewPhone
 5        .then(showOff)
 6        .then(function (fulfilled) {
 7            console.log(fulfilled);
 8        })
 9        .catch(function (error) {
10            console.log(error.message);
11        });
12    console.log('after asking mom'); // log after
13}

预期输出的序列是什么?您可以预期:

11. before asking Mom
22. Hey friend, I have a new black Samsung phone.
33. after asking mom

然而,实际的输出序列是:

11. before asking Mom
22. after asking mom
33. Hey friend, I have a new black Samsung phone.

Output

你不会停止玩,等待妈妈的承诺(新手机)。这就是我们称之为不同步的内容:代码将运行,而不会阻断或等待结果。

以下是 ES5 的完整示例:

 1// ES5: Full example
 2
 3var isMomHappy = true;
 4
 5// Promise
 6var willIGetNewPhone = new Promise(
 7    function (resolve, reject) {
 8        if (isMomHappy) {
 9            var phone = {
10                brand: 'Samsung',
11                color: 'black'
12            };
13            resolve(phone); // fulfilled
14        } else {
15            var reason = new Error('mom is not happy');
16            reject(reason); // reject
17        }
18
19    }
20);
21
22// 2nd promise
23var showOff = function (phone) {
24    var message = 'Hey friend, I have a new ' +
25                phone.color + ' ' + phone.brand + ' phone';
26
27    return Promise.resolve(message);
28};
29
30// call our promise
31var askMom = function () {
32    willIGetNewPhone
33    .then(showOff) // chain it here
34    .then(function (fulfilled) {
35            console.log(fulfilled);
36            // output: 'Hey friend, I have a new black Samsung phone.'
37        })
38        .catch(function (error) {
39            // oops, mom don't buy it
40            console.log(error.message);
41            // output: 'mom is not happy'
42        });
43};
44
45askMom();

在 ES5, ES6/2015, ES7 / 接下来的承诺

ES5 - 大多数浏览器

该演示代码可以在ES5环境(所有主要浏览器 + NodeJs)中工作,如果您添加了 Bluebird的承诺库。

ES6 / ES2015 - 现代浏览器,NodeJs v6

演示代码在盒子中工作,因为ES6支持原生承诺,此外,通过ES6函数,我们可以通过箭头函数进一步简化代码,并使用constlet

以下是 ES6 代码中的完整示例:

 1//_ ES6: Full example_
 2
 3const isMomHappy = true;
 4
 5// Promise
 6const willIGetNewPhone = new Promise(
 7    (resolve, reject) => { // fat arrow
 8        if (isMomHappy) {
 9            const phone = {
10                brand: 'Samsung',
11                color: 'black'
12            };
13            resolve(phone);
14        } else {
15            const reason = new Error('mom is not happy');
16            reject(reason);
17        }
18
19    }
20);
21
22// 2nd promise
23const showOff = function (phone) {
24    const message = 'Hey friend, I have a new ' +
25                phone.color + ' ' + phone.brand + ' phone';
26    return Promise.resolve(message);
27};
28
29// call our promise
30const askMom = function () {
31    willIGetNewPhone
32        .then(showOff)
33        .then(fulfilled => console.log(fulfilled)) // fat arrow
34        .catch(error => console.log(error.message)); // fat arrow
35};
36
37askMom();

请注意,所有var都被const所取代,所有function(resolve, reject)都被简化为(resolve, reject) =>

ES7 - Async / 等待

ES7引入了asyncWait语法,这使得不同步语法更容易理解,而没有.then.catch

用 ES7 语法重写我们的例子:

 1// ES7: Full example
 2const isMomHappy = true;
 3
 4// Promise
 5const willIGetNewPhone = new Promise(
 6    (resolve, reject) => {
 7        if (isMomHappy) {
 8            const phone = {
 9                brand: 'Samsung',
10                color: 'black'
11            };
12            resolve(phone);
13        } else {
14            const reason = new Error('mom is not happy');
15            reject(reason);
16        }
17
18    }
19);
20
21// 2nd promise
22async function showOff(phone) {
23    return new Promise(
24        (resolve, reject) => {
25            var message = 'Hey friend, I have a new ' +
26                phone.color + ' ' + phone.brand + ' phone';
27
28            resolve(message);
29        }
30    );
31};
32
33// call our promise in ES7 async await style
34async function askMom() {
35    try {
36        console.log('before asking Mom');
37
38        let phone = await willIGetNewPhone;
39        let message = await showOff(phone);
40
41        console.log(message);
42        console.log('after asking mom');
43    }
44    catch (error) {
45        console.log(error.message);
46    }
47}
48
49// async await it here too
50(async () => {
51    await askMom();
52})();

承诺和何时使用它们

為什麼我們需要承諾?在承諾之前,世界是怎樣的?在回答這些問題之前,讓我們回到基本上。

正常函数 VS Async 函数

讓我們來看看這兩個例子,兩個例子都加兩個數:一個使用正常函數加,另一個使用遠程加。

正常函数添加两个数字

1// add two numbers normally
2
3function add (num1, num2) {
4    return num1 + num2;
5}
6
7const result = add(1, 2); // you get result = 3 immediately
Async 函数添加两个数字
1// add two numbers remotely
2
3// get the result by calling an API
4const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
5// you get result  = "undefined"

如果您使用正常函数添加数字,您会立即获得结果,但是当您发出远程呼叫以获取结果时,您需要等待,并且无法立即获得结果。

您不知道是否会得到结果,因为服务器可能会下滑,响应速度慢等您不希望整个过程在等待结果时被阻止。

调用API、下载文件和阅读文件是您通常执行的一些非同步操作。

您不需要为非同步通话使用承诺。在承诺之前,我们使用了回调。回调是当您收到回调结果时呼叫的函数。

 1// add two numbers remotely
 2// get the result by calling an API
 3
 4function addAsync (num1, num2, callback) {
 5    // use the famous jQuery getJSON callback API
 6    return $.getJSON('http://www.example.com', {
 7        num1: num1,
 8        num2: num2
 9    }, callback);
10}
11
12addAsync(1, 2, success => {
13    // callback
14    const result = success; // you get result = 3 here
15});

后续 Async 行动

相反,我们要一次加一个数字,我们要加三次,在正常的函数中,我们会这样做:

 1// add two numbers normally
 2
 3let resultA, resultB, resultC;
 4
 5 function add (num1, num2) {
 6    return num1 + num2;
 7}
 8
 9resultA = add(1, 2); // you get resultA = 3 immediately
10resultB = add(resultA, 3); // you get resultB = 6 immediately
11resultC = add(resultB, 4); // you get resultC = 10 immediately
12
13console.log('total' + resultC);
14console.log(resultA, resultB, resultC);

这就是Callbacks的样子:

 1// add two numbers remotely
 2// get the result by calling an API
 3
 4let resultA, resultB, resultC;
 5
 6function addAsync (num1, num2, callback) {
 7    // use the famous jQuery getJSON callback API
 8    // https://api.jquery.com/jQuery.getJSON/
 9    return $.getJSON('http://www.example.com', {
10        num1: num1,
11        num2: num2
12    }, callback);
13}
14
15addAsync(1, 2, success => {
16    // callback 1
17    resultA = success; // you get result = 3 here
18
19    addAsync(resultA, 3, success => {
20        // callback 2
21        resultB = success; // you get result = 6 here
22
23        addAsync(resultB, 4, success => {
24            // callback 3
25            resultC = success; // you get result = 10 here
26
27            console.log('total' + resultC);
28            console.log(resultA, resultB, resultC);
29        });
30    });
31});

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

这种语法由于深度嵌入的呼叫反馈而不那么用户友好。

避免深深嵌套的呼叫回报

承诺可以帮助您避免深深的反馈,让我们看看相同示例的承诺版本:

 1// add two numbers remotely using observable
 2
 3let resultA, resultB, resultC;
 4
 5function addAsync(num1, num2) {
 6    // use ES6 fetch API, which return a promise
 7    // What is .json()? https://developer.mozilla.org/en-US/docs/Web/API/Body/json
 8    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
 9        .then(x => x.json()); 
10}
11
12addAsync(1, 2)
13    .then(success => {
14        resultA = success;
15        return resultA;
16    })
17    .then(success => addAsync(success, 3))
18    .then(success => {
19        resultB = success;
20        return resultB;
21    })
22    .then(success => addAsync(success, 4))
23    .then(success => {
24        resultC = success;
25        return resultC;
26    })
27    .then(success => {
28        console.log('total: ' + success)
29        console.log(resultA, resultB, resultC)
30    });

通过承诺,我们用.then平衡了回调。从某种意义上说,它看起来更干净,因为没有回调插座. 使用 ES7async语法,您可以进一步增强这个示例。

观察者

在你放弃承诺之前,有一些东西已经帮助你处理被称为可观察的非同步数据。

让我们来看看用 Observables 编写的相同演示文本,在这个例子中,我们将使用 RxJS 用于可观察的内容。

 1let Observable = Rx.Observable;
 2let resultA, resultB, resultC;
 3
 4function addAsync(num1, num2) {
 5    // use ES6 fetch API, which return a promise
 6    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
 7        .then(x => x.json());
 8
 9    return Observable.fromPromise(promise);
10}
11
12addAsync(1,2)
13  .do(x => resultA = x)
14  .flatMap(x => addAsync(x, 3))
15  .do(x => resultB = x)
16  .flatMap(x => addAsync(x, 4))
17  .do(x => resultC = x)
18  .subscribe(x => {
19    console.log('total: ' + x)
20    console.log(resultA, resultB, resultC)
21  });

例如,延迟函数添加3秒只使用一个代码行或重复,以便您可以重复调用一定数量次。

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

你可以阅读我的RxJ的帖子之一(https://andsky.com/tech/tutorials/rxjs-operators-for-dummies-forkjoin-zip-combinelatest-withlatestfrom)。

结论

熟悉反馈和承诺是很重要的。理解它们并使用它们. 不要担心现在的可观察性。

Published At
Categories with 技术
Tagged with
comments powered by Disqus