0%

js-promise

promise是一种异步方式处理值的方法,promise是对象,代表了一个函数最终可能的返回值或者抛出的异常。主要在与远程对象打交道时会非常有用。

promise简单的来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果)。

promise对象有两个特点:

  • 对象的状态不受外界影响。promise有三种状态:pending进行中、fulfilled已成功、rejected已失败。只有异步操作的结果可以决定当前是哪一种状态。
  • 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变只有两种可能:pending -> fulfilled,或者pending -> rejected。只要这两种状态发生了,就一直保持这个结果,称为resolved(已定型。

有了promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更加容易。

但是,promise的缺点是无法取消,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,promise内部抛出的错误不会反应到外部。再者,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

基本用法

Promise是一个构造函数,用来生成promise实例。

1
2
3
4
5
6
let promise = new Promise(function(resolve, reject) {
// 异步成功
resolve(value);
// 异步失败
reject(error);
};

构造函数接受一个函数为参数,该函数的两个参数分别是:resolve函数和reject函数。它们由JavaScript引擎提供。

  • resolve函数的作用是,将promise对象的状态从pending变成fulfilled(未完成-成功)。在异步操作成功时调用,并将异步操作的结果作为参数与传递出去。
  • reject函数的作用是,将promise对象的状态从pending变成rejected(未完成-失败)。以异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。

promise实例生成后,可以使用then方法分别指定resolve状态和rejected状态的回调函数。

1
2
3
4
5
promise.then(function(value){
// success
}, function(error){
// failure
});

then方法接受两个回调函数作为参数,第一个回调函数是当对象的状态变为resolved时调用,第二个回调函数是当promise对象的状态变为rejected时调用。其中,第二个函数是可选的。
例如,我们定义一个一段时间后才返回的异步操作,然后用promise来处理返回的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function setTimeoutMs(ms: number): Promise<any> {
return new Promise((resolve, reject) => {
console.log('hi')
setTimeout(function(){
resolve('test');
console.log('done');
}, ms);
});
}
setTimeoutMs(10000).then(data => {
console.log(data);
}, err => {
console.log(err);
});

promise实例化后立即执行,所以上面的输出顺序为:hi----done-------test

Promise.prototype.then()

then方法是定义在原型对象Promise.prototype上的。它的作用是为promise实例添加状态改变时的回调函数。then方法返回的是一个新的promise的实例(不是原来的promise),因此可以采用链式写法。

采用链式的then可以指定一组按照次序调用的回调函数。这时,前一个回调函数有可能返回的还是一个promise对象(即有异步操作),而后一个回调函数就会等待该promise对象的状态发生变化,再被调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
User.get(fromId).then(function(user){
// 找到了用户,找朋友
return user.friends.find(toId);
}, function(err){
// 没找到用户
}).then(function(friend){
// 找到了朋友,发送消息
return user.sendMessage(friend, message);
},function(err){
// 用户的朋友返回了异常
}).then(function(success){
// 给朋友发送了消息
}, function(err){
// 发生错误了
});

Promise.prototype.catch()

catch方法是then(null, rejection)的别名。用于指定发生错误时的回调。

1
2
3
4
5
this.setTimeoutMs(4000).then(data => {
console.log(data);
}).catch(err => {
console.log('err', err);
});

promise对象的错误具有冒泡性质,会一直向后传递,直到捕获位置。也就是说,错误总是会被下一个catch语句捕获。

一般来说,不要在then方法中定义rejected状态的回调函数,而应总是使用catch方法。

跟传统的try/catch代码不同的是,入股偶没有使用catch方法指定错误处理的回调函数,promise对象抛出的错误不会传递到外层代码。即不会有任何反应。

Promise.all()

all方法用于将多个promise实例包装成一个新的promise的实例。

1
let p = Promise.all([p1, p2, p3]);

Promise.all方法接受的参数为一个具有Iterator接口,且返回的每个成员都是Promise实例。

最后的状态分为两种情况:

  • 只有所有promise实例的状态都为fulfilled,p的状态才会变成fulfilled,此时,所有的promise实例的返回值组成一个数组,传递给p的回调函数。
  • 只要参数里面的其中一个被rejected了,p的状态就变成了rejected。此时第一个被rejected的实例的返回值会传递给p的回调函数。

如何让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
32
33
34
35
36
37
38

let a = this.getJson('a');
let b = this.getJson('b');
let c = this.getJson('c');
this.queue([a, b, c]).then(data => {
console.log('data', data);
});
// 链式调用
function queue(arr) {
let sequence = Promise.resolve();
arr.forEach(function(item) {
sequence = sequence.then((data) => {
console.log('promise data', data);
return item();
});
});
return sequence;
}
// 创建异步调用的方法
function getJson(name: string) {
return function() {
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.open('GET', '/assets/data/' + name + '.json', true);
req.onload = () => {
if (req.status === 200) {
return resolve(req.responseText);
} else {
return reject(new Error(req.statusText));
}
};
req.onerror = () => {
return reject(new Error(req.statusText));
};
req.send();
});
};
}

这样,可以根据数组的顺序来决定异步方法的调用。

码字辛苦,打赏个咖啡☕️可好?💘