译者:cherryvenus
Javascript Promises 不难。然而,许多人刚开始接触这个概念的时候,觉得有些难以理解。因此,我写下了我是如何理解 Promise 的,用一个通俗易懂的方法。
理解 Promise
Promise 简介:
""假设你是一个 宝宝 . 你的妈妈 承诺(Promise) 你,下个礼拜她会给你一台 新手机 。""
你_不知道_,下个礼拜你是否会拿到手机。你的妈妈可以_真的给你买_一个全新的手机,或者_放你鸽子_,也有可能如果她不开心:(了就扣下了手机。
这个就是 承诺(Promise) 。一个 Promise 有3个状态。他们分别是:
- Promise 是 待定的(pending) : 你不知道你下个礼拜能不能拿到手机。
- Promise 是 已解决的(resolved) :你的妈妈真的给你买了一个全新的手机。
- Promise 是 被拒绝的(rejected) : 因为你妈妈不开心所以不给你手机了。
创建一个 Promise
让我们将这个转化为 JavaScript。
/* ES5 */
var isMomHappy = false;
// Promise
var willIGetNewPhone = new Promise(
function (resolve, reject) {
if (isMomHappy) {
var phone = {
brand: 'Samsung',
color: 'black'
};
resolve(phone); // 完成了
} else {
var reason = new Error('妈妈不开心');
reject(reason); // reject
}
}
);
复制代码
代码本身颇具表现力。
-
我们用一个布尔值
isMomHappy
,来定义妈妈是否开心。 -
我们有一个命名为
willIGetNewPhone
的 Promise 。这个 Promise 可以是已完成的(resolved)
(如果妈妈给你买了一个新手机)或者被拒绝的(rejected)
(妈妈不开心,她没有给你买一个)。 -
这里有一个标准的语法来新建一个
Promise
,参考 MDN 文档 ,一个 promise 语法看上去像这样。
// promise 语法看上去像这样
new Promise(/* executor*/ function (resolve, reject) { ... } );
复制代码
-
你需要记住的是,当结果是正确的,在你的 promise 中调用
resolve(正确的值)
。在我们的例子中,如果妈妈很开心,我们就会拿到手机。因此,我们调用resolve
函数和phone
这个变量。如果我们的妈妈不开心,我们会调用reject
函数和一个理由(reason)reject(reason)
;
使用 Promise
现在,我们有一个 Promise。来看看怎么使用它:
/* ES5 */
...
// 调用我们的 Promise
var askMom = function () {
willIGetNewPhone
.then(function (fulfilled) {
// 太好啦, 你获得了一个新手机
console.log(fulfilled);
// output: { brand: 'Samsung', color: 'black' }
})
.catch(function (error) {
// 好不幸,你妈妈没买手机
console.log(error.message);
// output: '妈妈不开心'
});
};
askMom();
复制代码
-
我们有一个名为
askMom
的函数。在这个函数中,我们会使用 PromisewillIGetNewPhone
。 -
一旦 Promise 被解决(resolved)或者被拒(rejected),我门希望采取些措施。我们用
.then
和.catch
来实现。 -
在我们的例子中,
.then
之中有个function(fulfilled) { ... }
。fulfilled
是什么?fulfilled
就是是你传入 Promise 的resolve(your_success_value)
.因此,在我们例子中就是phone
。 -
我们在
.catch
中有function(error){ ... }
。error
是什么?正如你猜测的,error
正是你传入 Promise 中的reject(your_fail_value)
。因此,在我们的例子中就是reason
。
让我们看看例子运行之后的结果吧!
Demo: jsbin.com/nifocu/1/ed…
串联 Promise
Promiss 是可串联的。
也就是说,你,宝宝, 承诺(Promise) 你的小伙伴,当你妈妈给你买了手机,你就会 给他们看 新手机。
这就是另一个 Promise 啦。我们来写一个!
// 简略
...
// 第二个 promise
var showOff = function (phone) {
return new Promise(
function (resolve, reject) {
var message = 'hey 伙计,我有个新 ' +
phone.color + ' ' + phone.brand + '手机';
resolve(message);
}
);
};
复制代码
说明:
-
在这个例子中,你可能意识到我们没有调用
reject
。因为这个是可选的参数。 -
我们可以简化这个样例就像用
Promise.resolve
代替。
// 简略
...
// 第二个 promise
var showOff = function (phone) {
var message = 'hey 伙计,我有个新 ' +
phone.color + ' ' + phone.brand + ' 手机';
return Promise.resolve(message);
};
复制代码
让我们串联 Promise。你,宝宝只能在
willIGetNewPhone
Promise 实现之后,才能开始
showOff
Promise。
...
// 调用 Promise
var askMom = function () {
willIGetNewPhone
.then(showOff) // 在这里串联
.then(function (fulfilled) {
console.log(fulfilled);
// output: 'Hey 伙计, 我有一个新的黑色三星手机。'
})
.catch(function (error) {
// 好不幸,你妈妈没买手机
console.log(error.message);
// output: '妈妈不开心'
});
};
复制代码
串联 Promise 简单吧!
Promises 是异步的
Promise 是异步的。让我们在调用 Promise 之前和之后各打印一个信息。
// 调用我们的Ppromise
var askMom = function () {
console.log('询问妈妈之前'); // 运行之前打印
willIGetNewPhone
.then(showOff)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error) {
console.log(error.message);
});
console.log('询问妈妈之后'); // 运行之后打印
}
复制代码
预计的输出序列是怎么样的?也许你预计是这样的
1\. 询问妈妈之前
2\. Hey 伙计, 我有一个新的黑色三星手机。
3\. 询问妈妈之后
复制代码
然而, 真实的输出顺序是这样的:
1\. 询问妈妈之前
2\. 询问妈妈之后
3\. Hey 朋友, 我有一个新的黑色三星手机。
复制代码
为什么? 因为生命 (或者 JavaScript) 不等人。
宝宝在玩的时候等待着妈妈的承诺(promise) (新手机).不是吗? 这个我们称之为
异步(asynchronous)
,
代码不会因为阻塞或等待结果而不运行. 任何想等待 Promise 之后再运行的, 你需要把他们放入
.then
.
ES5, ES6/2015, ES7/Next 中的 promise
ES5 - 大多数浏览器
demo代码在 ES5 的环境(所有主流浏览器+NodeJs)中是可以运行,如果你包含了 Bluebird Promise 库。这是因为 ES5 不支持直接调用 Promise 。另一个有名的 Promise 库是 Kris Kowal 的 Q 。
ES6 / ES2015 - 现代浏览器, NodeJs v6
demo代码可以直接调用,因为ES6支持本地 Promise。外加,和 ES6 函数
fat arrow =>
,以及
const
和
let
搭配使用,我们可以进一步简化代码`。
这里是 ES6 代码的例子
/* ES6 */
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);
}
}
);
const showOff = function (phone) {
const message = 'Hey 伙计, 我有个一个新' +
phone.color + ' ' + phone.brand + '手机';
return Promise.resolve(message);
};
// 调用我们的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 */
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('妈妈不开心');
reject(reason);
}
}
);
// 2nd promise
async function showOff(phone) {
return new Promise(
(resolve, reject) => {
var message = 'Hey 伙计, 我有一个新' +
phone.color + ' ' + phone.brand + '手机';
resolve(message);
}
);
};
// 调用 Promise
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 askMom();
})();
复制代码
-
每当你需要在函数中返回一个 Promise 的时候,你要在函数之前添加
async
。
E.g.
async function showOff(phone)
-
当你需要调用一个 promise,你需要在此之前添加
await
。
E.g.
let phone = await willIGetNewPhone;
and
let message = await showOff(phone);
.
-
使用
try { ... } catch(error) { ... }
来捕捉 Promise 错误, 被拒绝的 promise
为什么用 Promise 以及何时用他们?
为什么你需要 promise ?在 promise 之前我们是如何的?在回答这些问题之前,让我们回到基本原理。
普通函数 vs 异步函数
让我们看看这两个例子,他们都执行两个数字相加,一个用普通函数相加,一个用远程方法相加。
普通函数相加两个数字
// 正常相加数字
function add (num1, num2) {
return num1 + num2;
}
const result = add(1, 2); // you get result = 3 immediately
复制代码
异步函数相加两个数字
// 远程相加数字
// 调用api获得结果
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// you get result = ""undefined""
复制代码
如果你用普通函数相加两数字,你会马上获得结果。然而如果你发出一个远程调用来获得结果,那么你就需要等待,你不能马上得到结果。
或者这样说,你不知道会不会得到结果,因为服务器可能会性能下降,响应慢等等。你不希望因为等待着结果,让整个进程都被堵住。
调用API,下载文件,读取文件一些平时你会执行的异步操作。
Pomise 出现之前的世界: 回调(Callback)
我们一定要用 Prmoise 来做异步回调吗?不是的。优先于 promise ,我们用回调(callback)。回调(callback)仅仅是个你调用的函数,当你获得返回结果的时候。让我们修改之前的例子来获得一个回调。
// 远程相加两数字
// 调用API获得结果
function addAsync (num1, num2, callback) {
//使用有名的 jQuery getJSON 的回调 API
return $.getJSON('http://www.example.com', {
num1: num1,
num2: num2
}, callback);
}
addAsync(1, 2, success => {
// callback
const result = success; // 这里你得到 result = 3
});
复制代码
这个语法看上去OK,为什么我们之后需要用 Promise ?
如果你想做一系列的异步操作怎么办??
比如说,不同于一次仅仅相加两个数字,我们希望加3次。用普通的函数,我们这么做: