Promise 是什么?

Promise 是 ES6 中的一个重要特性,那 Promise 究竟长什么样子呢?我们来 console.dir()打印一下就知道了。

从打印的结果可以看出,Promise 是一个构造函数,身上有许多方法: all,any,race,reject,resolve;原型上有 then,catch 等常用到的方法。

那我们来 new 一个 Promise 来看看。

1
2
3
4
5
6
7
let pro = new Promise(function (resolve, reject) {
//** 在里面执行一些异步操作*/
setTimeout(function () {
console.log("三秒后执行");
resolve("数据data");
}, 3000);
});


Promise 构造函数接收一个参数,是一个函数,里面传入两个参数 resolve,reject;它们分别表示异步操作执行成功后的回调和异步操作执行失败的回调。

Promise 概念

Promise 的中文意思是‘承诺’,什么叫承诺?承诺就是现在没有发生,在将来的某个时刻一定会发生的事情。
放在编程语言的环境下,Promise 就是异步事件的结果的占位符。我们不用去管异步事件的结果什么时候来,只需要关心异步事件的结果产生的时候,你想要做什么就对了。

生命周期

异步事件不是立即执行程序,它的结果可能要在动作发生后一段时间才到,所以它有个生命周期。
一个 Promise 的生命周期主要有 2 个阶段:

1
2
1: unsettled(pending) 处理中
2: settled (fulfilled或者rejected) 处理完

Promise 有三个状态:

  • pending   初始状态(等待)
  • fulfiled   操作成功
  • rejected   操作失败

resolve: 是指将 Promise 对象的状态从 pending 变为 fulfilled,在异步操作成功时调用,并将结果作为参数传递出去。
reject: 是指将 Promise 对象的状态从 pending 变为 rejected,在异步操作失败时调用,并将异步操作报的错误作为参数传递出去。

上面这段代码,执行了一个 setTimeout 异步操作,会在三秒后执行,打印“三秒后执行”并调用 resolve 方法。
需要注意的一点是,我们只是 new 了一个 Promise 对象并没有调用它,但传进去的函数去执行了;所以,一般使用 Promise 的时候我们都会在外面包一层函数,在需要的时候再去调用这个函数。

1
2
3
4
5
6
7
8
9
10
11
promiseAsync(){
let pro = new Promise(function(resolve,reject){
setTimeout(function(){
console.log('三秒后执行')
resolve('数据data')
},3000)
})
return pro
}

promiseAsync()

链式调用

1
2
3
promiseAsync().then(function (data) {
console.log(data);
});

在 promiseAsync()的返回上直接调用 then 方法,then 接收一个参数,是函数,并且会拿到我们在 promiseAsync 中调用 resolve 时传的的参数。运行这段代码,会在三秒后输出“三秒后执行”,紧接着输出“数据 data”。

从这可以看出,原来 then 里面的函数就跟我们平时的回调函数一个意思,能够在 promiseAsync 这个异步任务执行完成之后被执行。这就是 Promise 的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。
而 Promise 的优势在于,可以在 then 方法中继续写 Promise 对象并返回,然后继续调用 then 来进行回调操作。
所以,Promise 表面上只是简化了层层回调的写法,实质上通过维护状态、传递状态的方式来使得回调函数能够及时得到调用,相比于传递 callback 来说更加简单和灵活。
如下实例:

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
function promiseAsync1() {
let pro1 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("promiseAsync1执行成功");
resolve("data1111");
}, 3000);
});
return pro1;
}

function promiseAsync2() {
let pro2 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("promiseAsync2执行成功");
resolve("data2222");
}, 3000);
});
return pro2;
}

function promiseAsync3() {
let pro3 = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("promiseAsync3执行成功");
resolve("data3333");
}, 3000);
});
return pro3;
}

//*链式调用*/
promiseAsync1()
.then(function (data) {
console.log(data); // data1111
return promiseAsync2();
})
.then(function (data) {
console.log(data); // data2222
return promiseAsync3();
})
.then(function (data) {
console.log(data); // data3333
return "直接返回数据";
})
.then(function (data) {
console.log(data); // 直接返回数据
});

运行结果:

总结:
1、主要用于异步计算
2、可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
3、可以在对象之间传递和操作 Promise,帮助我们处理队列

错误处理

Promise 会自动捕获内部异常,并交给 rejected 响应函数进行处理。

1. throw new Error(‘错误信息’).catch( () => {错误处理逻辑})

1
2
3
4
5
6
7
8
9
10
11
new Promise(function (resolve, reject) {
setTimeout(function () {
throw new Error("抛出error");
}, 3000);
})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log("Error", err);
});

2. reject(‘错误信息’).then(() => {}, () => {错误处理逻辑})

1
2
3
4
5
6
7
8
9
10
11
12
new Promise(function (resolve, reject) {
setTimeout(function () {
reject("抛出err");
}, 3000);
}).then(
(res) => {
console.log(res);
},
(err) => {
console.log("Error", err);
}
);

推荐使用第一种方式,更加清晰好读,并且可以捕获前面所有的错误(可以捕获 N 个 then 回调错误)


Promise.reject 执行失败操作

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
function getNum() {
var pro = new Promise(function (resolve, reject) {
//做一些异步操作
setTimeout(function () {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
if (num <= 5) {
resolve(num);
} else {
reject("数字太大了");
}
}, 3000);
});
return pro;
}

getNum().then(
function (data) {
console.log("resolved");
console.log(data);
},
function (reason, data) {
console.log("rejected");
console.log(reason);
}
);

getNum 函数用来异步获取一个数字,3 秒后执行完成,如果数字小于等于 5,我们认为是“成功”了,调用 resolve 修改 Promise 的状态。否则我们认为是“失败”了,调用 reject 并传递一个参数,作为失败的原因。


Promise.catch

catch 方法和 then 方法的第二参数是差不多的作用,主要是用来指定 reject 的回调。还是拿 getNum 函数为例,用法如下:

1
2
3
4
5
6
7
8
9
getNum()
.then((res) => {
console.log("resolved");
console.log(res);
})
.catch((e) => {
console.log("rejected");
console.log(e);
});

效果和写在 then 的第二个参数里面一样。不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 catch 方法中。请看下面的代码:

1
2
3
4
5
6
7
8
9
getNum()
.then((res) => {
console.log("resolved");
cosnole.log(reslut); // 一个未定义的变量
})
.catch((e) => {
console.log("rejected");
console.log(e); // ReferenceError: cosnole is not defined at <anonymous>:3:5
});

在 resolve 的回调中,我们 console.log(reslut);而 reslut 这个变量是没有被定义的。如果我们不用 Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:


也就是说进到 catch 方法里面去了,而且把错误原因传到了 e 参数中。即便是有错误的代码也不会报错了,这与我们的 try/catch 语句有相同的功能。

Promise.all 批量执行操作

Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

1
2
3
4
Promise.all([pro1,pro2,pro3])用于将多个Promise实例,包装成一个新的Promise实例。
他接收一个数组作为参数,数组里可以是Promise对象或者别的值,只有Promise会等待状态改变。
当所有的子Promise实例全部完成,该Promise才完成,返回值是全部值的数组。
有任何一个子Promise实例失败,该Promise将失败,并返回第一个失败的子Promise实例的结果。

还是拿上面定义的 promiseAsync1,promiseAsync2,promiseAsync3 来用一下:

1
2
3
4
5
6
7
8
Promise.all([promiseAsync1(), promiseAsync2(), promiseAsync3()])
.then((res) => {
console.log("全部值数组:");
console.log(res);
})
.catch((e) => {
console.log("第一个实例失败结果:" + e);
});

有了 all,就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

promise.race 类似于 Promise.all() ,区别在于它有任意一个完成就算完成

all 方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是 race 方法,这个词本来就是赛跑的意思。race 的用法与 all 一样,我们把上面 promsieAsync1 的延时改为 1 秒来看一下:

1
2
3
4
5
6
7
8
Promise.race([promiseAsync1(), promiseAsync2(), promiseAsync3()])
.then((res) => {
console.log("第一个执行完成:");
console.log(res);
})
.catch((e) => {
console.log(e);
});

这三个异步操作同样是并行执行的。结果你应该可以猜到,1 秒后 promiseAsync1 已经执行完了,此时 then 里面的就执行了。结果是这样的:

  • 常见用法:
    回调包装成 Promise,他有两个显而易见的好处:
    1. 可读性好
    2. 返回的结果可以加入任何 Promise 队列

异步操作和定时器放在一起,如果定时器先触发,就认为超时,告知用户;
例如我们要从远程的服务器在资源如果 5000ms 还没有加载过来我们就告知用户加载失败

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
//请求某个图片资源
function requestImg() {
var pro = new Promise(function (resolve, reject) {
var img = new Image();
img.onload = function () {
resolve(img);
};
img.src = "xxxxxx";
});
return pro;
}

//延时函数,用于给请求计时
function timeout() {
var pro = new Promise(function (resolve, reject) {
setTimeout(function () {
reject("图片请求超时");
}, 5000);
});
return pro;
}

Promise.race([requestImg(), timeout()])
.then((res) => {
console.log(res);
})
.catch((e) => {
console.log(e);
});
🔨 Promise(二):手动实现一个Promise