美文网首页
理解Promise - 学习

理解Promise - 学习

作者: Dannn_Y | 来源:发表于2018-08-05 21:13 被阅读43次

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

    相关文章

      网友评论

          本文标题:理解Promise - 学习

          本文链接:https://www.haomeiwen.com/subject/tzhivftx.html