理解Promise - 学习

2018-08-05  本文已影响43人  Dannn_Y

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上面例子的代码片段,如有需要可以在原文中查看。

上一篇下一篇

猜你喜欢

热点阅读