Promise学习和使用
Promise是JS从用来处理异步的框架。他是一个对象,用来传递异步操作的消息,他的结果只有在未来才知道(成功或者失败),在ES6中已经内置了 Promise对象。他的好处有:
- 使用Promise可以避免回调地狱,可以链式调用
- 在JS事件队列的当前运行完成之前,回调函数永远不会被调用。
- 通过then形式添加的回调函数,甚至都在异步操作完成之后被添加的函数,都会被调用。
Promise基本使用
一般我们new一个Promise对象,当然可以是作为result返回给一个方法或者赋值给一个变量,然后通过then获取Promise执行结果,成功(resolve)或者是失败(reject),这里有一个必须清楚:Promise对象一旦创建立即执行。所以一般来说Promise具有以下三个状态:
has-resolution-Fulfilled
:resolve被调用之后,调用onFulfiled(也就是then的处理成功参数)has-rejection
:reject被调用之后,调用Rejected的(也即是then的处理error函数)unresolve
:Pending,待定,就是Promise刚刚创建的时候。
简单例子:解析:创建一个Promise对象,他传入一个可选function,function的参数分别是resolve和reject,用来处理成功或者是失败,这两个参数对于Promise来说也是可选的。然后resolve/reject的调用会去到then里面被对应的状态函数调用。1
2
3
4
5
6
7
8
9
10let p = new Promise(function (resolve,reject) {
setTimeout(()=>{
//只会输出hello
resolve("hello");
resolve("hi");
},1000)
});
p.then(value => {
console.log(value);
});
还有一点需要注意的:Promise的状态一单改变,将不可修改,比如已经调用了resolve()状态,那么再去调用reject或者是resolve已经没有作用了
Promise的then和catch以及finally
- 多个then连用对于多个then连用,每下一个then都会等待前一个then返回的Promise执行完改变状态之后再去执行,假如上一个then没有返回一个新的Promise对象,那么他将会立即执行。对于在then中,使用匿名函数或者是其他方式,非then的onFulfilled返回值,后面的then也会执行并且将会使得这个Promise的onFulfilled丢失。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58//多个then连用
//每一个onFulfiled中返回一个新的Promise继续执行异步,这样子可以形成一个链式串行执行。
const start = new Date().getTime();
new Promise(function (resolve, reject) {
console.log(1);
if (reject) {
setTimeout(() => {
console.log(new Date().getTime() - start);
resolve("first");
}, 1000)
} else {
reject("error");
}
}).then(value => {
console.log(2);
console.log(value);
//返回新的Promise
return new Promise(function (resolve) {
setTimeout(() => {
console.log(new Date().getTime() - start);
resolve("second")
}, 2000);
})
}).then(value => {
console.log(3);
console.log(new Date().getTime() - start);
console.log(value);
//直接执行下一个then
setTimeout(() => {
console.log(new Date().getTime() - start + " no waring")
}, 1000);
return value;
}).then(value => {
//没有返回,所以下一个then的onFulfiled的值是undefined
console.log(4);
console.log(value);
console.log(new Date().getTime() - start);
}).then(value => {
console.log(5);
console.log(value);
console.log(new Date().getTime() - start);
});
//输出结果
1
1018
2
first
3026
3
3026
second
4
second
3026
5
undefined
3026
4027 no waring
then嵌套:对于上一层的then函数,假如他是返回了一个新的Promise实例并且引入了它内部的then,那么外部的下一个then将会在内部执行完成之后再去执行,例如。
1 | new Promise(resolve => { |
结果是按照1,2,3,4,5,6,7打印。
陷阱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let one = function () {
return new Promise(resolve => {
setTimeout(() => {
console.log("1");
resolve("q");
},1000)
});
};
let two = function () {
return new Promise(resolve => {
setTimeout(() => {
console.log("2");
resolve("w");
},1000)
});
};
one().then(two()).then(value => {
console.log("end")
console.log(value)
});对于1和2,将会几乎同时输出,因为对于tow而已,他是一个函数,是执行状态,里面的Promise,一声明立即会被执行。而在one()的第一个then当中,返回的是一个Promise对象,而不是一个函数,所以,tow()的then会被忽略,在最后一个then中,响应了one的resolve。修改为
one().then(two)
,去除括号既可是正常流程()。异常处理
catch
两种方式,第一种可以直接写在then中,也就是交给Rejected函数处理then(null,Rejected)
,第二种是使用catch,catch其实是Rejected的别名函数。
第一种方式,使用Rejected的方式1
2
3
4
5
6
7
8
9
10
11
12
13const x = 1;
new Promise(function (resolve, reject) {
if (x > 1) {
resolve(x);
} else {
// reject("x <=1");,不能定位到具体行,使用下面方式可以
throw new Error("x<=1");
}
}).then(value => {
console.log(value);
}, error => {
console.log(error);
});使用第二种方式:推荐使用,可以捕获全部的错误,最后在结尾进行处理,同时,尽量可以使用throw的方式,这样子可以定位到具体出错行。
1
2
3
4
5
6
7
8
9
10
11
12
13const y = 2;
new Promise(function (resolve, reject) {
if (y > 2) {
resolve(x);
} else {
reject("y <=2");//不能定位到具体行,使用下面方式可以
// throw new Error("x<=1");
}
}).then(value => {
console.log(value)
}).catch(error=>{
console.log(error);
});finally
‘finally’方法用于指定不管Promise状态如何,最终都会执行,他是没有参数的,也就是finally的执行是与Promise的执行状态无关的。它相当于then
和catch
连用,只是无参数传入。
我们知道onFulfiled处理resolve,Rejected处理reject。对于一个then的情况,这当然是比较好处理,但是,假如我们有多个then,每一个then之后继续接then或者then当中返回了一个新的Promise对象,这些操作某一个出现了错误,那么是之前的Rejectd无法处理的,我们就可以在末端添加一个catch统一处理这些异常。
对于catch,他也是跟then一样,可以连用,返回一个Promise,当catch执行之后,catch内部没有继续发生错误,那么catch之后的then可以正常的继续往下执行。
Promise的高阶函数
Promise.all
他可以批量执行一系列的Promise实例。接受一个数组,可以是Promise对象,也可以是普通的对象(包括方法),但是只有Promise对象才会等待状态改变。只有所有的Promise状态改变,才会执行then。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32const p1 = new Promise(function (resolve) {
console.log("p1");
setTimeout(() => {
console.log("p1->");
resolve(1);
}, 4000)
});
const p2 = new Promise(function (resolve) {
console.log("p2");
setTimeout(() => {
console.log("p2->");
resolve(2);
}, 2000)
});
const p3 = new Promise(function (resolve) {
console.log("p3");
setTimeout(() => {
console.log("p3->");
resolve(3);
}, 3000)
});
Promise.all([p1,p2,p3]).then(value => {
console.log(value);
});
//输出
p1
p2
p3
p2->
p3->
p1->
[ 1, 2, 3 ]下输出p1,p2,p3是因为一旦Promise创建,就立即执行。
对于all方法:他的onFulfilled函数会返回所有Promise的每一个执行结果,按照数组中的顺序,把每一个Promise的值组合成数组返回。当包含非Promise对象的时候,也会把他按照参数顺序组合到结果当中。当其中一个或者多个Promise出现错误的时候,则会只返回第一个出现错误的Promise的error。Promise.race()
他与Promise.all()
一样,也是接受一个数组对象,可以把多个promise对象包装成一个新的Promise实例,当一旦有某一个promies实例发生改变的时候,那么这个包装的Promise实例就会发生改变。当参数中包含非promise对象的时候,就会先调用Promise.resolve()方法,将参数转化为promise对象,再去执行。
需要注意的是,虽然包装的Promise实例已经改变,旧的那些Promise数组对象还是会继续执行,只是他们的状态已经是废弃了的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const p = new Promise(function (resolve) {
setTimeout(() => {
console.log(1);
resolve(1);
}, 2000);
});
const p1 = new Promise(function (resolve) {
setTimeout(() => {
console.log(2);
resolve(2);
}, 1000);
});
const p2 = new Promise(function (resolve) {
setTimeout(() => {
console.log(3);
resolve(3);
}, 3000);
});
Promise.race([p,p1,p2]).then(value => {
console.log(value);
});
//结果
2
2
1
3Promise.resolve()
他可以将现有对象转化为Promise对象。
1 假如他的参数是一个Promise对象,那么它将不做任何修改,原封不动返回该实例
2 假如参数是一个thenable
对象,那么他将会把该对象转化为Promise对象,然后立即执行thenable的then方法,then执行完成之后就会该Promise对象状态就会变为fulfilled。thenable
对象是指具有then方法的对象,比如1
2
3
4
5let thenable = {
then:function(resolve,reject){
resolve();
}
}3 假如参数不是Promise对象,也不是thenable对象,可以是普通对象或者是方法,那么将会返回一个新的Promise对象,状态为resolve。
4 不带任何参数,那么他将返回一个resolved状态的Promise对象。
例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26Promise.resolve(new Promise(function (resolve) {
setTimeout(() => {
resolve(1);
}, 1000);
})).then(value => {
console.log(value);
});
const fun = {
then: function (resolve, reject) {
reject("ok")
}
}
Promise.resolve(fun)
.then(value => {
console.log("ok");
}).catch(error=>{
console.log("error");
})
Promise.resolve("hi").then(value => console.log(value));
Promise.resolve().then(()=>{return new Promise(function (resolve) {
setTimeout(() => {
console.log("fix")
}, 2000);
})}).then(value => {
console.log(value);
})Promise.reject()
他与Promise.resolve()
类似,也是返回一个新的Promise对象,但是该对象的状态是reject的,而且他会忽略thenable对象,不会说去执行该对象的then方法(除非主动调用)。
Promise难点
- 一个Promise p1对象中resolve另外一个Promise p2对象。
这时候p1对象的then的调用会等待p2的状态改变定型。比如p2是Pending状态,那么等待p2状态改变,p1的then才会收到回调。假如p2是fulFilled/Reject的状态,则会立刻调用p2的then。例如1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const p1 = new Promise(function (resolve, reject) {
console.log("start");
setTimeout(() => {
console.log("ii");
resolve("iii");
}, 2000)
});
const p2 = new Promise(function (resolve, reject) {
// ...
console.log("jj");
resolve(p1);
});
p2.then(value => {
console.log("kk");
console.log(value)
});
//输出
start
jj
ii
kk
iii