理解Promise - 学习

JavaScript是同步编程语言,但是我们可以通过回调函数,使他看起来像异步编程语言。

Promise为了解决什么?
Node.js用回调函数代替了事件,使异步编程在JavaScript上更加流行。但当更多程序开始使用异步编程时,事件和回调函数却不能满足开发者想要做的所有事情,它们还不够强大,而Promise就是这些问题的解决方案。

Understanding promises in JavaScript 这篇文章描述了两个部分用于理解 promise,一是创建promise,二是处理promise。本文是在学习此文的基础上加入了一些自己的理解,大部分代码都是学习原文作者。原文内容更丰富,建议阅读原文。

作者在帮助理解Promise上举了很多例子,在阅读的过程中最好打开浏览器的控制台,边看边执行代码验证结果,帮助理解。而且例子贴近生活更便于理解。

创建Promise

创建一个promise标准的写法如下

new Promise( /* executor */ function(resolve, reject) { ... } );

这个构造函数接收一个执行函数,执行函数接收两个参数resolvereject。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);

想知道结果,请把代码复制下来在浏览器控制台执行看看吧。


image.png

由于这个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为止。


image.png

10秒钟过后,promise执行了resolve 方法,PromiseStatusPromiseValue因此被更新。你可以看到,我们可以传递一个JSON对象代替一个简单string来更新resolve函数。因此我们也可以传递其他的数据到resolve函数中。

image.png

接下来让我们看看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浏览器将会显示错误提示。你可以先忽略,我们接下来会解释。


image.png

如我们所看到的PromiseStatus有三个不同的值。pending resolvedrejected,当promise创建PromiseStatus 将会在pending状态下,此时的PromiseValueundefined 知道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,正如下面这张图片所示,会引起连锁反应。


image.png

下面作者举了一个例子,来帮助理解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"
  );
});

输出如下


image.png

如果我们改变 momSavings到200000,愿望达成,输出如下

image.png

我们模拟数据输出,这样我们就可以看到怎么有效的使用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);
});

输出如下:


image.png

下面的两个方法帮助你处理 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);
});
image.png

我们从结果中得出两个重要的结论

  1. 第三个promise花了两秒完成,上一个promise花了4秒完成。但是正如你看到的输出仍然保持有序的状态
  2. 上面的程序增加了一个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);
});
image.png

因为数组中没有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);
});
image.png

当执行到失败程序时,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);
});
image.png

所以的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);
});
image.png

所有的promise并行执行。第四个promise在3秒内reject。只要这个完成,Promise.race返回rejected

综上: Promise.race()传入的promise数组中,总是返回最先执行完的一个,不管是reject还是resolved


作者在文章的最后也贴出了code gist上面例子的代码片段,如有需要可以在原文中查看。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • title: promise总结 总结在前 前言 下文类似 Promise#then、Promise#resolv...
    JyLie阅读 12,098评论 1 21
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,555评论 1 56
  • Promise含义 Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更强大。所谓Pr...
    oWSQo阅读 1,078评论 0 4
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,329评论 0 19
  • //本文内容起初摘抄于 阮一峰 作者的译文,用于记录和学习,建议观者移步于原文 概念: 所谓的Promise,...
    曾经过往阅读 1,202评论 0 7