介绍
Javascript Promises可能很難理解,因此,我想寫下我理解承諾的方式。
理解承诺
一个短暂的承诺:
`想象一下你是一个孩子,你的妈妈答应你,她下周会给你一个手机。
你不知道你是否会得到那部手机直到下周,你的妈妈真的可以给你买一部全新的手机,或者她不会。
这是一个承诺,一个承诺有三个状态,它们是:
- 等待:你不知道你是否会得到那个电话
- 完成:妈妈是 happy,她给你买了一个全新的电话
- 拒绝:妈妈是 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
链接承诺
承诺是可靠的。
让我们告诉你,孩子,答应你的朋友,当你妈妈给你买一台新手机时,你会向他们展示新手机。
这是另一个承诺!让我们写下来!
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.
你不会停止玩,等待妈妈的承诺(新手机)。这就是我们称之为不同步
的内容:代码将运行,而不会阻断或等待结果。
以下是 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函数,我们可以通过箭头函数进一步简化代码,并使用const
和let
。
以下是 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引入了async
和Wait
语法,这使得不同步语法更容易理解,而没有.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)。
结论
熟悉反馈和承诺是很重要的。理解它们并使用它们. 不要担心现在的可观察性。