node.js 如何使用Promise

Promise提供了一种异步执行模式。
注意一点Promise的执行仍然是异步方式的,并没有改变成同步执行模式,只不过让代码写起来读起来像是同步执行一样。

Promise是使用主要分两步:

第一步,定义Promise

var p = new Promise((resolve, reject) => {
    //
    // promise body
    // if (...) {
    //  resolve(value)
    // } else {
    //  reject(reason)
    // }
    //
});

注意3点:

  1. 定义Promise时需要一个参数,这个参数是一个函数,我把它叫做Promise函数体
function (resolve, reject) { ... }
  1. Promise函数体接受两个参数
// 参数resolve和reject也是一个函数,原型定义如下:
// function resolve(value) {...}
// function reject(reason) {...}
  1. Promise函数体不需要返回值。
    Promise函数体的返回是通过回调参数函数resolve和reject来标记函数成功失败;如果成功则调用resolve()函数,如果失败则调用reject()函数。

请比较Promise函数体原型,和Promise.then函数体原型,他们是一样的,他们应该就是一样的。

第二步,定义resolve和reject参数函数

前面Promise函数体里面用到了resolve和reject函数,但彼时只是形参而已,这两个函数并没有被定义出来;那么他们在哪里定义的呢,Promise的then和catch函数就是真正用来定义resolve和reject函数体的。
函数then和catch本身是Promise的一个内置函数(注意区分两个概念:then函数,和then函数的参数函数):

promise.then(function(value) {
    // body
}).catch(function(reason) {
    // body
})

then和catch函数都接收一个函数作为参数,这个参数函数原型定义

function (value) { ... }

我们可以看到这个函数原型定义和Promise定义时的参数resolve和reject的原型是一样的,这就明白了吧。

  1. promise.then()函数的返回值是一个Promise对象,这是为了构造then()函数链。
    promise.catch()函数的返回值也是一个Promise对象(尽管我们通常不会使用这个对象)。
  2. then()函数参数函数的返回值,是作为下一个then函数链的输入参数。
    如果返回值是一个Promise对象除外,后面有分析。
  3. 这里要弄清楚then()函数的返回值,和then函数参数函数的返回值。
    3.1 then函数的定义是Promise内置定义的,它的代码,输入输出都由Promise实现,不是用户设置的;也就是用户根本看不见then函数的返回语句。
    3.2 then函数参数函数是传递给then()的那个用户定义函数,这个函数体是由用户定义的,所以其返回值都是由用户提供的,如下代码可以返回一个数字,字符串,或者对象都可以。
     1  function foo(param) {
     2      var p = new Promise((resolve, reject) => {
     3          if (param == 1) { 
     4              resolve(param)
     5          }
     6          else {
     7              reject("invalid value " + param)
     8          }
     9      });
    10      return p
    11  }
    12
    13  foo(1)
    14  .then((value) => {
    15      console.log("then-1: " + value);
    16      return "2"
    17  })
    18  .then((value) => {
    19      console.log("then-2: " + value);
    20  })
    21  .catch((reason) => {
    22      console.log("catch: " + reason);
    23  })

运行结果:

$ node p.js
then-1: 1
then-2: 2
  • 第15行then-1的输出 value是第4行resolve函数的参数值。
  • 第19行then-2的输出 value是第16行return的值,即前一个then函数体的返回值作为下一个then函数体的参数。

我们看到在then()链中,一个then函数体里的返回值是作为下一个then函数体的参数使用的,那么如何在一个then函数体里面标记一个reject状态呢,因此此时也不是一个Promise定义函数,也就没有reject函数形参可用。

  1. 办法1: 也是最直白的办法是抛出异常,还是以上面代码为例:
    ...
    13  foo(1)
    14  .then((value) => {
    15      console.log("then-1: " + value);
    16      throw "2"
    17  })
    18  .then((value) => {
    19      console.log("then-2: " + value);
    20  })
    21  .catch((reason) => {
    22      console.log("catch: " + reason);
    23  })

我们把第16行的return语句改成了throw语句,运行结果:

$ node p.js  
then-1: 1
catch: 2
  1. 办法2:再定义一个Promise并返回
    13  foo(1)
    14  .then((value) => {
    15      console.log("then-1: " + value);
    16      return new Promise((resolve, reject) => {
    17          if (value == 1) { 
    18              console.log("then-1-1: " + value);
    19              resolve(100)
    20          }
    21          else {
    22              reject("invalid value " + value)
    23          }
    24      });
    25  })
    26  .then((value) => {
    27      console.log("then-2: " + value);
    28  })
    29  .catch((reason) => {
    30      console.log("catch: " + reason);
    31  })

在then-1里面可以返回一个值给then-2使用,或者抛出一个异常直接到catch分支,也可以定义一个Promise对象,在新定义的Promise函数体里面使用resolve或者reject以决定走then-2还是catch异常分支。

$ node p.js  
then-1: 1
then-1-1: 1
then-2: 100
  • 第19行resolve的结果是吧值100传给then-2作为参数。
  • 注意这里then函数参数函数里面返回一个具体值和返回一个Promise对象的差异
    • 如果返回一个普通值,则把值直接传给后面的一个then作为参数。
    • 如果返回一个Promise对象,则根据promise的resolve和reject决定后面的then参数值。

补充一点,其实then()函数的原型有两个参数

po.then(function(value) {  // success },
        function(value) {  // failure }
       );

分别对应Promise最终状态是resolve和reject,而通常为了构造Promise链才只提供一个参数函数,即resolve参数,而把所有的reject函数参数统一到catch函数里面。

Promise相关代码的执行顺序

看一个例子来说明问题,

console.log("main-1");
function foo(param) {
    var p = new Promise((resolve, reject) => {
        console.log("promise-1");
        if (param == 1) { 
            resolve("1")
        } else {
            reject("2")
        }
        console.log("promise-2");
    });
    return p
}

console.log("main-2");

foo(1).then((value) => {
    console.log("then-1");
}).catch((reason) => {
    console.log("catch-1");
})

console.log("main-3");

执行结果如下:

$ node promise.js
main-1
main-2
promise-1
promise-2
main-3
then-1

所以

  1. Promise函数体是在Promise对象创建的时候就被执行了,可以理解为Promise函数是同步执行的。
    由此我们知道resolve或者reject函数已经在Promise函数体内被调用了,而此时resolve和reject的值并没有被定义了,怎么办?其实这就是Promise机制实现的功能,可是先调用一个未定义的函数,等将来函数被定义的时候(then())在真正执行函数体。
  2. then/catch函数体并不是在then/catch被调用的时候执行的,而是在后面的某一个异步时间点被执行,这也是Promise机制实现的功能。
    因此定义Promise时指定的函数体是在当场就执行的,而定义then()时指定的函数体不是当场执行,而是在之后以异步的方式执行的。

简单的说

  1. Promise对象创建的时候,立刻执行Promise函数体,同时会标记将来是执行resolve还是reject
    多说一句Promise对象的最终状态只有两个要么是resolved,要么是rejected,所以如果在Promise函数体里面同时调用了resolve和reject(注意Promise函数体里面不管是调用了resolve还是reject,都不结束函数,而会继续执行后面的代码),谁先调用谁有效,后面调用的无效,因为第一个调用就已经改变了Promise的最终状态。举个例子:
     1  function foo(param) {
     2      var p = new Promise((resolve, reject) => {
     3          resolve("succ");
     4          reject("fail");
     5          console.log("hello");
     6          return "any";
     7          console.log("world");
     8      });
     9      return p
    10  }
    11
    12  foo(1)
    13  .then((value) => {
    14      console.log("then-1: " + value);
    15  })
    16  .catch((reason) => {
    17      console.log("catch: " + reason);
    18  })

运行结果为:

$ node p.js
hello
then-1: succ
  • 第3行resolve把promise最终状态职位resolved.
  • 第4行reject代码无效,因为Promise的最终状态已经被置为resolved了,此时再置成rejected自然无效。
  • 第5行log代码正常执行
  • 第6行return返回,这个值不知道返回到哪里去了。
  • 第7行没有执行,因为第6行已经返回了。
  1. then/catch负责注册这些函数体到对应的resolve/reject函数链上,而不会马上就执行他们,只是注册。
  2. 对他们的执行是在稍后以异步事件的方式回调的;具体的回调时间是不确定的。

后面还有Promise.all()等没有研究,主要是暂时没有用到;以后有机会再整理。

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

推荐阅读更多精彩内容