JavaScript是同步编程语言,但是我们可以通过回调函数,使他看起来像异步编程语言。
Promise为了解决什么?
Node.js用回调函数代替了事件,使异步编程在JavaScript上更加流行。但当更多程序开始使用异步编程时,事件和回调函数却不能满足开发者想要做的所有事情,它们还不够强大,而Promise就是这些问题的解决方案。
Understanding promises in JavaScript 这篇文章描述了两个部分用于理解 promise,一是创建promise,二是处理promise。本文是在学习此文的基础上加入了一些自己的理解,大部分代码都是学习原文作者。原文内容更丰富,建议阅读原文。
作者在帮助理解Promise上举了很多例子,在阅读的过程中最好打开浏览器的控制台,边看边执行代码验证结果,帮助理解。而且例子贴近生活更便于理解。
创建Promise
创建一个promise标准的写法如下
new Promise( /* executor */ function(resolve, reject) { ... } );
这个构造函数接收一个执行函数,执行函数接收两个参数resolve
和reject
。Promise一般用于处理一些简单的异步程序和代码块,比如文件程序,API调用,DB调用,IO操作。异步程序初始化是在 executor 这个函数中初始化。如果这个异步程序执行成功,使用resolve函数返回,如果执行失败使用 reject函数返回。
下面创建一个简单的promise函数,在浏览器控制台执行下面的程序
var keepsHisWord;
keepsHisWord = true;
promise1 = new Promise(function(resolve, reject) {
if (keepsHisWord) {
resolve("The man likes to keep his word");
} else {
reject("The man doesnt want to keep his word");
}
});
console.log(promise1);
想知道结果,请把代码复制下来在浏览器控制台执行看看吧。
由于这个promise立马就执行了,我们没有办法在这个promise中检查初始化情况。所以让我们再重新创建一个promise,这个promise需要点时间去resolve。简单的办法就是使用 setTimeout函数。
promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({
message: "The man likes to keep his word",
code: "aManKeepsHisWord"
});
}, 10 * 1000);
});
console.log(promise2);
上方的代码只是创建了一个promise,在10秒钟之后无条件的 resolve。So,我们可以检查这个状况的promise,知道它resolve为止。
10秒钟过后,promise执行了resolve 方法,PromiseStatus
和PromiseValue
因此被更新。你可以看到,我们可以传递一个JSON对象代替一个简单string来更新resolve函数。因此我们也可以传递其他的数据到resolve函数中。
接下来让我们看看promise中的reject函数,简单修改上面的函数,如下:
keepsHisWord = false;
promise3 = new Promise(function(resolve, reject) {
if (keepsHisWord) {
resolve("The man likes to keep his word");
} else {
reject("The man doesn't want to keep his word");
}
});
console.log(promise3);
至此,我们创建了一个无法处理的reject promise,chrome浏览器将会显示错误提示。你可以先忽略,我们接下来会解释。
如我们所看到的PromiseStatus
有三个不同的值。pending
resolved
和 rejected
,当promise创建PromiseStatus
将会在pending
状态下,此时的PromiseValue
是undefined
知道promise resolved
或者rejected
。当一个promise在resolved
或者rejected
状态下,这个promise可以说是settled
已经被解决。所以一个promise的状态通常是从 pending状态 到 settled状态。
上面我们已经知道了怎么创建promise,接下来我们将要学习如何使用和处理promise,手把手教你怎么理解Promise
对象。
理解promise对象
Promis在MDN文档中解释如下
Promise对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。
Promise 对象有静态方法和原型方法,静态方法在Promise对象中可以被申请为独立的。记住不管是普通的方法还是原型方法,只要返回一个Promise对象,就会变得简单。
原型方法
promise有三个原型方法。所有的这些方法,处理不同的状态。正如上文的例子当一个Promise被创建,最开始是pending状态,下面的三个方法将会被执行,不管返回的是 fulfilled 或者 rejected 都会被解决
Promise.prototype.catch(onRejected)
Promise.prototype.then(onFulfilled, onRejected)
Promise.prototype.finally(onFinally)
下面这张图片展示了 .then .catch方法。如果返回一个Promise,正如下面这张图片所示,会引起连锁反应。
下面作者举了一个例子,来帮助理解Promise。
例如:你是个学生,想让你妈妈给你买个手机,她说:“我将在这个月底给你买个手机”
让我们看看这个在JavaScript中怎么实现,如果这个承诺在月底执行。
var momsPromise = new Promise(function(resolve, reject) {
momsSavings = 20000;
priceOfPhone = 60000;
if (momsSavings > priceOfPhone) {
resolve({
brand: "iphone",
model: "6s"
});
} else {
reject("We donot have enough savings. Let us save some more money.");
}
});
momsPromise.then(function(value) {
console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
});
momsPromise.catch(function(reason) {
console.log("Mom coudn't buy me the phone because ", reason);
});
momsPromise.finally(function() {
console.log(
"Irrespecitve of whether my mom can buy me a phone or not, I still love her"
);
});
输出如下
如果我们改变 momSavings
到200000,愿望达成,输出如下
我们模拟数据输出,这样我们就可以看到怎么有效的使用then和catch
.then 可以同时标记onFulfilled,onRejected handlers,我们可以将它们写在一起,代替分开的写法,我们可以使用 .then处理两种情况,如下:
momsPromise.then(
function(value) {
console.log("Hurray I got this phone as a gift ", JSON.stringify(value));
},
function(reason) {
console.log("Mom coudn't buy me the phone because ", reason);
}
);
除了可读性强了一些,最好还是分开写吧。
为了更好的理解Promise,让我们创建一个函数返回promise,将会随机的返回resolved或者rejected,这样我们就可以测试多种情况。
function getRandomNumber(start = 1, end = 10) {
//works when both start,end are >=1 and end > start
return parseInt(Math.random() * end) % (end-start+1) + start;
}
下面将创建一返回promise的函数,使用随机函数,随机生成一个数,如果大于5将resolved,小于5返回 rejected,
function getRandomNumber(start = 1, end = 10) {
//works when both start and end are >=1
return (parseInt(Math.random() * end) % (end - start + 1)) + start;
}
var promiseTRRARNOSG = (promiseThatResolvesRandomlyAfterRandomNumnberOfSecondsGenerator = function() {
return new Promise(function(resolve, reject) {
let randomNumberOfSeconds = getRandomNumber(2, 10);
setTimeout(function() {
let randomiseResolving = getRandomNumber(1, 10);
if (randomiseResolving > 5) {
resolve({
randomNumberOfSeconds: randomNumberOfSeconds,
randomiseResolving: randomiseResolving
});
} else {
reject({
randomNumberOfSeconds: randomNumberOfSeconds,
randomiseResolving: randomiseResolving
});
}
}, randomNumberOfSeconds * 1000);
});
});
var testProimse = promiseTRRARNOSG();
testProimse.then(function(value) {
console.log("Value when promise is resolved : ", value);
});
testProimse.catch(function(reason) {
console.log("Reason when promise is rejected : ", reason);
});
// Let us loop through and create ten different promises using the function to see some variation. Some will be resolved and some will be rejected.
for (i=1; i<=10; i++) {
let promise = promiseTRRARNOSG();
promise.then(function(value) {
console.log("Value when promise is resolved : ", value);
});
promise.catch(function(reason) {
console.log("Reason when promise is rejected : ", reason);
});
}
刷新浏览器控制台,在控制台中执行上面的函数,观察不同的输出情况 resolve 和 reject。
静态方法
在Promise对象中,这里有四个静态方法
前两个方法可以快速的创建resolved 或者 rejected promise函数
帮助你创建一个rejected promise
Promise.reject(reason)
var promise3 = Promise.reject("Not interested");
promise3.then(function(value){
console.log("This will not run as it is a resolved promise. The resolved value is ", value);
});
promise3.catch(function(reason){
console.log("This run as it is a rejected promise. The reason is ", reason);
});
帮助你创建一个resolved promise
Promise.resolve(value)
var promise4 = Promise.resolve(1);
promise4.then(function(value){
console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.catch(function(reason){
console.log("This will not run as it is a resolved promise", reason);
});
一个promise可以有多个处理程序,更新上面的代码如下
var promise4 = Promise.resolve(1);
promise4.then(function(value){
console.log("This will run as it is a resovled promise. The resolved value is ", value);
});
promise4.then(function(value){
console.log("This will also run as multiple handlers can be added. Printing twice the resolved value which is ", value * 2);
});
promise4.catch(function(reason){
console.log("This will not run as it is a resolved promise", reason);
});
输出如下:
下面的两个方法帮助你处理 promises。当你处理多个promises,最好的方法是创建一个promises数组,然后在设置promises的时候做必要的操作。下面创建两个方法,一个将在几秒钟之后resolve,另一个在几秒钟之后reject。
var promiseTRSANSG = (promiseThatResolvesAfterNSecondsGenerator = function(
n = 0
) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({
resolvedAfterNSeconds: n
});
}, n * 1000);
});
});
var promiseTRJANSG = (promiseThatRejectsAfterNSecondsGenerator = function(
n = 0
) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject({
rejectedAfterNSeconds: n
});
}, n * 1000);
});
});
Promise.All
MDN 文档解释如下
Promise.all(iterable)方法返回一个
Promise
实例,此实例在iterable
参数内所有的promise都完成(resolved)或参数中不包含promise时回调完成(resolve);如果参数中promise有一个失败(rejected),此实例回调失败(reject),失败原因的是一个失败promise
结果。
示例1:当所有的promise都完成(resolved)。大多数都会设想这种情况。
console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(2));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
console.timeEnd("Promise.All");
console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
console.log("One of the promises failed with the following reason", reason);
});
我们从结果中得出两个重要的结论
- 第三个promise花了两秒完成,上一个promise花了4秒完成。但是正如你看到的输出仍然保持有序的状态
- 上面的程序增加了一个timer用于记录
Promise.All
花了多长时间。如果promise是按顺序执行的需要花费 1+4+2=7秒。但是从我们的timer中可以看到只花费了4秒。这可以证明所有的promises是并行执行的。
示例2:当没有promises会发生什么
console.time("Promise.All");
var promisesArray = [];
promisesArray.push(1);
promisesArray.push(4);
promisesArray.push(2);
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
console.timeEnd("Promise.All");
console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
console.log("One of the promises failed with the following reason", reason);
});
因为数组中没有promises,返回的promises是已完成的(resolved)
示例3:当只有一个promises返回失败时会怎么样
console.time("Promise.All");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(1));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRSANSG(3));
**promisesArray.push(promiseTRJANSG(2));**
promisesArray.push(promiseTRSANSG(4));
var handleAllPromises = Promise.all(promisesArray);
handleAllPromises.then(function(values) {
console.timeEnd("Promise.All");
console.log("All the promises are resolved", values);
});
handleAllPromises.catch(function(reason) {
console.timeEnd("Promise.All");
console.log("One of the promises failed with the following reason ", reason);
});
当执行到失败程序时,promises里面停止并返回reject信息
综上: Promise.all()
只有在所有的promise数组都resolve时才会返回所有完成的数据。但要是数组中有一个promise任务失败,Promise.all()
就会返回当前失败的promise信息,而就算其他的promise任务执行成功,也会返回reject。可以这么理解,Promise.all()
返回结果要么是所有的,要么什么都没有。
Promise.race
MDN文档说明
Promise.race(iterable)方法返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的promise就会解决或拒绝。
示例1:promises优先解决
console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(3));
promisesArray.push(promiseTRSANSG(2));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
console.timeEnd("Promise.race");
console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
console.timeEnd("Promise.race");
console.log("The fastest promise rejected with the following reason ", reason);
});
所以的promises并行执行,第三个promise在2秒内完成,只要这个promise完成,Promise.race
被解决。
示例2:当promises中reject程序优先完成
console.time("Promise.race");
var promisesArray = [];
promisesArray.push(promiseTRSANSG(4));
promisesArray.push(promiseTRSANSG(6));
promisesArray.push(promiseTRSANSG(5));
promisesArray.push(promiseTRJANSG(3));
promisesArray.push(promiseTRSANSG(4));
var promisesRace = Promise.race(promisesArray);
promisesRace.then(function(values) {
console.timeEnd("Promise.race");
console.log("The fasted promise resolved", values);
});
promisesRace.catch(function(reason) {
console.timeEnd("Promise.race");
console.log("The fastest promise rejected with the following reason ", reason);
});
所有的promise并行执行。第四个promise在3秒内reject。只要这个完成,Promise.race
返回rejected
综上: Promise.race()
传入的promise数组中,总是返回最先执行完的一个,不管是reject还是resolved
作者在文章的最后也贴出了code gist上面例子的代码片段,如有需要可以在原文中查看。