代码世界

node.js 如何使用Promise

2017-11-21  本文已影响32人  CodingCode

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

我们看到在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

补充一点,其实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
  1. then/catch负责注册这些函数体到对应的resolve/reject函数链上,而不会马上就执行他们,只是注册。
  2. 对他们的执行是在稍后以异步事件的方式回调的;具体的回调时间是不确定的。

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

上一篇 下一篇

猜你喜欢

热点阅读