Promise提供了一种异步执行模式。
注意一点Promise的执行仍然是异步方式的,并没有改变成同步执行模式,只不过让代码写起来读起来像是同步执行一样。
Promise是使用主要分两步:
第一步,定义Promise
var p = new Promise((resolve, reject) => {
//
// promise body
// if (...) {
// resolve(value)
// } else {
// reject(reason)
// }
//
});
注意3点:
- 定义Promise时需要一个参数,这个参数是一个函数,我把它叫做Promise函数体
function (resolve, reject) { ... }
- Promise函数体接受两个参数
// 参数resolve和reject也是一个函数,原型定义如下:
// function resolve(value) {...}
// function reject(reason) {...}
- 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的原型是一样的,这就明白了吧。
- promise.then()函数的返回值是一个Promise对象,这是为了构造then()函数链。
promise.catch()函数的返回值也是一个Promise对象(尽管我们通常不会使用这个对象)。 - then()函数参数函数的返回值,是作为下一个then函数链的输入参数。
如果返回值是一个Promise对象除外,后面有分析。 - 这里要弄清楚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: 也是最直白的办法是抛出异常,还是以上面代码为例:
...
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
- 办法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
所以
- Promise函数体是在Promise对象创建的时候就被执行了,可以理解为Promise函数是同步执行的。
由此我们知道resolve或者reject函数已经在Promise函数体内被调用了,而此时resolve和reject的值并没有被定义了,怎么办?其实这就是Promise机制实现的功能,可是先调用一个未定义的函数,等将来函数被定义的时候(then())在真正执行函数体。 - then/catch函数体并不是在then/catch被调用的时候执行的,而是在后面的某一个异步时间点被执行,这也是Promise机制实现的功能。
因此定义Promise时指定的函数体是在当场就执行的,而定义then()时指定的函数体不是当场执行,而是在之后以异步的方式执行的。
简单的说
- 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行已经返回了。
- then/catch负责注册这些函数体到对应的resolve/reject函数链上,而不会马上就执行他们,只是注册。
- 对他们的执行是在稍后以异步事件的方式回调的;具体的回调时间是不确定的。
后面还有Promise.all()等没有研究,主要是暂时没有用到;以后有机会再整理。