介绍
Javascript Promises可能难以理解。所以,我想写下我对promise的理解。
理解承诺
简短的承诺:
“想象一下你是个孩子。你妈妈向你保证,下周她会给你买一部新手机。”
你不知道下周能不能拿到那部手机。你妈妈真的可以给你买一部全新的手机,或者她不会。
那是一个承诺。一个承诺有三个状态。他们是:
- 待定:你不知道你是否会得到那部手机
- 应验:妈妈很高兴,她给你买了一部全新的手机
- 拒绝:妈妈不高兴,她不给你买手机
创建承诺
让我们将其转换为 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
}
}
);
代码本身就很有表现力。
以下是 promise 语法的正常外观:
// promise syntax look like this
new Promise(function (resolve, reject) { ... } );
消费承诺
现在我们有了 promise,让我们消费它:
// 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 didn't buy it
console.log(error.message);
// output: 'mom is not happy'
});
};
askMom();
让我们运行示例并查看结果!
演示:https : //jsbin.com/nifocu/1/edit?js,console
链接承诺
Promise 是可链接的。
假设你,孩子,向你的朋友保证,当你妈妈给你买一部新手机时,你会向他们展示新手机。
那是另一个承诺。让我们写吧!
// 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);
}
);
};
注意:我们可以将上面的代码写成下面这样:
// 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);
};
让我们把承诺串联起来。你,孩子,只能在showOff
承诺之后开始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'
});
};
这就是您可以链接承诺的方式。
Promise 是异步的
Promise 是异步的。让我们在调用 promise 之前和之后记录一条消息。
// 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
}
预期输出的顺序是什么?你可能会期待:
1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom
但是,实际的输出顺序是:
1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.
在等待妈妈的承诺(新手机)时,您不会停止玩游戏。这就是我们所说的异步:代码将运行而不会阻塞或等待结果。任何需要等待承诺进行的东西都被放入.then
.
这是 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();
ES5、ES6/2015、ES7/Next 中的 Promise
ES5 – 大多数浏览器
如果您包含Bluebird承诺库,演示代码在 ES5 环境(所有主要浏览器 + NodeJs)中是可行的。这是因为 ES5 不支持开箱即用的 Promise。另一个著名的承诺库是Kris Kowal 的Q。
ES6 / ES2015 – 现代浏览器,NodeJs v6
演示代码开箱即用,因为 ES6 原生支持 promise。此外,借助 ES6 函数,我们可以进一步简化代码,使用箭头函数并使用const
和let
。
这是 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();
请注意,所有的var
都替换为const
. 所有的function(resolve, reject)
都被简化为(resolve, reject) =>
. 这些变化带来了一些好处。
ES7 – 异步/等待
ES7 介绍async
和await
语法。它使异步语法更易于理解,无需使用.then
和.catch
。
用 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();
})();
Promise 以及何时使用它们
为什么我们需要承诺?在承诺之前,世界是怎样的?在回答这些问题之前,让我们先回到基本面。
普通函数 VS 异步函数
让我们来看看这两个例子。两个示例都执行两个数字的加法:一个使用普通函数进行加法,另一个是远程加法。
两个数相加的普通函数
// add two numbers normally
function add (num1, num2) {
return num1 + num2;
}
const result = add(1, 2); // you get result = 3 immediately
将两个数字相加的异步函数
// 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"
如果用普通函数将数字相加,则立即得到结果。但是,当您发出远程调用获取结果时,需要等待,并且无法立即获取结果。
您不知道是否会得到结果,因为服务器可能已关闭、响应缓慢等。您不希望整个过程在等待结果时被阻塞。
调用 API、下载文件和读取文件是您将执行的一些常用异步操作。
您不需要为异步调用使用承诺。在 promise 之前,我们使用了回调。回调是您在获得返回结果时调用的函数。让我们修改前面的示例以接受回调。
// 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
});
后续异步操作
我们不想一次添加一个数字,而是要添加三个数字。在正常功能中,我们会这样做:-
// 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);
这就是回调的样子:
// 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);
});
});
});
演示:https : //jsbin.com/barimo/edit?html,js,console
由于深度嵌套的回调,此语法不太用户友好。
避免深度嵌套的回调
Promise 可以帮助您避免深度嵌套的回调。让我们看一下同一个例子的 promise 版本:
// 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)
});
使用 promises,我们使用.then
. 在某种程度上,它看起来更干净,因为没有回调嵌套。使用 ES7async
语法,您可以进一步增强此示例。
可观察对象
在您接受 Promise 之前,有一些东西可以帮助您处理名为Observables
.
让我们看一下用 Observables 编写的同一个演示。在这个例子中,我们将使用RxJS作为 observables。
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)
});
Observables 可以做更多有趣的事情。例如,只需一行代码即可delay
添加函数3 seconds
或重试,以便您可以重试调用一定次数。
...
addAsync(1,2)
.delay(3000) // delay 3 seconds
.do(x => resultA = x)
...
你可以在这里阅读我的一篇 RxJs 帖子。
结论
熟悉回调和承诺很重要。了解它们并使用它们。暂时不要担心 Observables。根据情况,这三个因素都可以影响您的发展。