0%

Hello Promise

简介

Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise有三种状态:pendingfulfilledrejected

Promise对象初始化时状态为:pending(进行中)

调用resolve方法时,Promise的状态由pending变为fulfilled

调用rejected方法时,Promise的状态由pending变为rejected

1、 Promise对象的状态不受外界影响,只有异步操作的结果可以决定当前时哪一种状态,任何其他操作都无法改变这个状态。

2、 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected

使用

简单的初始化一个Promise

1
2
3
4
5
const promise = new Promise((resolve) => {
setInterval(() => {
resolve('延迟2s回调');
}, 2000);
});

上面初始化了一个Promise对象,并创建了一个异步操作:在两秒后调用resolve方法。这里仅仅是初始化了这个对象,并没有调用。继续调用这个promise对象

1
2
3
4
5
6
7
8
9
const promise = new Promise((resolve) => {
setInterval(() => {
resolve('延迟2s回调');
}, 2000);
});

promise.then((data) => {
console.log(data);
});

当执行代码的时候,控制台在两秒钟后打印出了“延迟2s回调”,也就是说在上面初始化Promise的resolve里面的参数回调到了下面then方法中的data参数中。简单来说就是then里面的函数,会传递到Promise中并由resolve来控制这个函数的执行时间以及执行所需要的参数并回调。

上述代码的执行过程就是Promise内部状态由pending变为fulfilled

Promise还有从pending变为rejected的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const promise = new Promise((resolve, reject) => {
setInterval(() => {
let number = getRandomNumber();
if(number > 5) {
resolve('大于5');
} else {
reject('不大于5');
}
}, 2000);
});

promise.then((data) => {
console.log('then');
console.log(data);
}).catch((error) => {
console.log('catch');
console.log(error);
});

Promise中初始化了一个简单逻辑,两秒之后获得一个随机数字,该数字大于5的时候调用resolve方法,反之则调用reject方法。由上面的例子可以得知,当数字大于5的时候会在then中打印。那么当数字小于5的时候,则会在catch中打印改数字。

简单总结下来,resolve方法回调至then中,reject方法回调至catch中。

那么可以借用Promise的特点,简单的封装一个网络请求。使用resolvereject分别包装正常返回和异常返回的值和信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const promise = new Promise((resolve, reject) => {
fetch(url)
.then((response) => {
return response.json();
})
.then((responseData) => {
resolve(responseData);
})
.catch(function (error) {
reject(error)
})
})

promise.then((data) => {
// 请求返回的数据
}).catch((error) => {
// 请求报错
})

链式调用

通过上面的例子Promise可以理解为是一个类似延迟加载的异步回调函数,那么其实也可以用普通的方法实现Promise的功能

1
2
3
4
5
6
7
8
9
10
myPromise = (callback) => {
setInterval(() => {
console.log('执行完成');
callback('随便什么数据');
}, 2000);
}

myPromise((data) => {
console.log(data);
})

它也会和Promise拥有同样的效果。那Promise到底能实现什么不可替代的功能呢?

假如有许多的异步操作需要执行,比如连续的三个请求,每一次请求都需要从前一个请求中获取参数,那么它的写法如下

1
2
3
4
5
6
7
8
9
10
fetch(url1).then((data1) => {
if (data1 == 200) {
fetch(url2).then((data2) => {
if(data2 == 200) {
fetch(url3) ....
...
}
})
}
})

虽然可以实现需求,然是代码看起来并不那么友好,它庞大的层级结构使之无法轻易被修改。如果要是需要在第二个请求和第三个请求中再加入一些异步操作,那么将是不可修改的,这被称为回调地狱(Callback hell)。

使用Promise即可解决上述问题,Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。也就是说可以一直return一个Promise对象,可以一直在在后面调用then方法。如果使用Promise后上述的需求可以写为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 分别将三个请求包装为函数
request1 = () => {
return fetch(url1);
}
request2 = () => {
return fetch(url2);
}
request3 = () => {
return fetch(url3);
}
// 调用时
request1().then((data)=> {
return request2();
}).then((data) => {
return request3();
}).then((data) => {
...
}).catch();

只需要在then中调用下一个Promise,上一个Promise中回调的数据即可传递下去。

关于回调地狱(Callback hell)的问题也可以使用async/await解决,类似的代码如下

1
2
3
4
5
6
7
8
9
10
11
task() async {
try{
String id = await request1();
String userInfo = await request2();
await request3();
//执行接下来的操作
} catch(e){
//错误处理
print(e);
}
}

相关API

all

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

1
const p = Promise.all([promise1, promise2, promise3]);

p的状态由promise1promise2promise3决定,分成两种情况。

(1)只有promise1promise2promise3的状态都变成fulfilledp的状态才会变成fulfilled,此时promise1promise2promise3的返回值组成一个数组,传递给p的回调函数。

(2)只要promise1promise2promise3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意,如果作为参数的 promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

race

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

1
const p = Promise.race([promise1, promise2, promise3]);

上面代码中,只要有任何一个promise改变状态,那么p的状态也会跟着改变。也就是说最快改变状态的promise会让其他promise不会回调。

可以通过这个方法来完成一个请求超时功能

1
2
3
4
5
6
7
8
9
10
const p = Promise.race([
fetch(url),
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);

p
.then(console.log)
.catch(console.error);

即当5s内请求没有返回结果,就返回请求超时。

参考链接