一项新技术的出现一定是为了解决某个痛点问题的。ES6的Promise就是为了解决ES5在处理异步任务时所存在的问题。Promise是一个异步处理框架。
Promise前夕——ES5对于异步任务的处理
我们先来看看ES5是怎么处理异步任务的。主要的手段就是通过回调函数。回调函数是JavaScript本身就支持的机制。可以通过回调函数实现类似下面这个的网络请求过程。虽然这个函数的实现看起来代码有点不好看,但是在使用的时候逻辑还是比较清晰的。
//1.实现主要逻辑
function getCities(path, onSuccess, onErr) {
if (path === "") {
//或者path格式有问题
onErr("请求失败,path不能为空");
} else {
//发生请求
console.log("开始发生请求");
console.log("数据请求中......");
setTimeout(()=>{
console.log("请求到数据了");
//假装获取到数据了
const data = ["广州", "上海", "温州"];
onSuccess(data);
},2000)
}
}
调用的时候,逻辑还是非常清晰的,成功和失败分别在两个回调函数得到处理。
//2.测试成功状况
console.log("..............测试成功请求...............");
getCities(
"/home/getCityData",
res => {
console.log(res);
},
err=> {
console.log(err);
}
);
..............测试成功请求...............
开始发生请求
数据请求中......
请求到数据了
[ '广州', '上海', '温州' ]
path为空,模拟失败的情况。
//3.测试失败状况
console.log("..............测试失败请求...............");
getCities("",
res => {
console.log(res);
},
err=> {
console.log(err);
}
);
..............测试失败请求...............
请求失败,path不能为空
总的来说,使用回调函数的方式实现异步任务的方式存在下面的问题:
1.函数的实现非常的难看。而且实现比较麻烦。
2.函数在调用的时候参数先后顺序是没有约定的,完全取决于设计者自身。
使用Promise对异步任务进行处理
既然ES5的回调函数方式存在一些问题,那么Promise是怎么解决的呢?我们来看一下promise的用法。把上面的代码逻辑改成用promise的实现。
Promise是一个类,里面有两个回调函数resolve和reject,实际上,这就相当于前面的onSuccess和onErr回调函数。
而且resolve和reject顺序是固定的,你不能随便改。除了这些之外,整个实现逻辑和ES5实际上是一样的。
function getCities(path) {
//1.使用promise处理
const promise = new Promise((resolve, reject) => {
if (path === "") {
//或者path格式有问题
reject("path不能为空");
} else {
//发生请求
console.log("开始发生请求");
console.log("数据请求中......");
setTimeout(() => {
console.log("请求到数据了");
//假装获取到数据了
const data = ["广州", "上海", "温州"];
resolve(data);
}, 2000);
}
});
return promise;
}
结果返回一个promise,通过then和catch方法分别处理成功和失败的情况。
const promose2=getCities("/home/getCityData");
promose2.then(value=>{
console.log("数据为:"+value);
})
promose2.catch(err=>{
console.log("失败的原因是:"+err);
})
简写方式:
promose2.then(value=>{
console.log("数据为:"+value);
}).catch(err=>{
console.log("失败的原因是:"+err);
})
光从代码量上来说,和ES5相比是差不多的。使用难度也是差不多的。只是Promise给出了限定,回调函数都是固定的,这样就解决了回调函数命名和顺序的问题。虽然实现的代码还是有点难看,但至少定义了标准,不会出现随意设计的情况。
如果去除业务逻辑,实际上主干逻辑就是下面这样,非常的简单清晰。
function getCities(path) {
const promise = new Promise((resolve, reject) => {
//成功的情况
resolve(data);
//失败的情况
reject("path不能为空");
});
return promise;
}
const promose2=getCities("/home/getCityData");
promose2.then(value=>{
console.log("数据为:"+value);
}).catch(err=>{
console.log("失败的原因是:"+err);
})
promise的特性和细节
执行和输出细节
promise的用法也不难,接下来看一下promise的特性,以及使用的时候都有哪些细节。
我们精简一下代码,写成下面这个样子。
function getCities(path) {
const promise = new Promise((resolve, reject) => {
resolve("xxx");
resolve("xxx");
console.log(".........");
reject("path不能为空");
});
return promise;
}
getCities("/home/getCityData")
.then(value=>{
console.log("数据为:"+value);
}).catch(err=>{
console.log("失败的原因是:"+err);
})
输出:
可以看到resolve方法只会执行一次,并且 console.log(“…”); 这行的输出还是在前面输出的。可见resolve函数是异步操作。reject方法也是一样的,可以自行测试。
.........
数据为:xxx
promise的三个状态
这里设计到promise的三个状态:
1.pending 等待状态
在new Promise后,resolve和reject函数执行前都是等待状态。
2.funfilled 兑现状态
resolve执行后。
3.rejected 拒绝状态
reject执行后。
一旦状态确定,就不可以再更改了。也不能再次执行回调函数。
executor函数
promise里面的这个函数会被立即执行,这个函数有个专门的名字叫executor。
const promise = new Promise((resolve, reject) => {
});
}
resolve细节
resolve函数的参数类型
resolve函数是可以传递容易数据类型的。下面的传值都是可以的,可以直接试下。
function getCityData(path){
return new Promise((resolve,reject)=>{
// resolve("Tom")
// resolve(10)
// resolve([1,2,3])
resolve({name:"Tom",age:18})
})
}
getCityData("/home/city").then(res=>{
console.log("数据是"+res);
})
比较特殊的情况是传的值是一个Promise。这时候then并不会立即接收到值,而是会接收来自传入的那个Promis的resolve传过来的值,并且可以多层嵌套。
function getCityData(path){
return new Promise((resolve,reject)=>{
console.log("..第二个Promise..");
resolve(new Promise((resolve,reject)=>{
console.log("..第二个Promise..");
resolve("在第二个Promise的resolve被处理")
}))
})
}
getCityData("/home/city").then(res=>{
console.log("数据是"+res);
})
..第二个Promise..
..第二个Promise..
数据是在第二个Promise的resolve被处理
这种写法在框架里面比较常见。一般使用的时候不会用到这种写法。
还有一种更加少用的写法,就是thenable的写法。
thenable写法(了解)
在给resolve传对象类型的时候,如果里面有个属性是then,那么就可以写一个函数,并且把前面的resolve作为then的参数传进去。实际上就是实现了类似两个Promise嵌套的情况。这种用法是非常少的,了解就行。
resolve({
name:"Tom",
then:function(resolve){
//异步,不保证顺序
console.log("........");
resolve("333")
console.log(">>>>>>>>");
}
})
getCityData("/home/city").then(res=>{
console.log("数据是"+res);
})
........
>>>>>>>>
数据是333
then方法的细节
then的另一种写法
then的另一种写法,这个和ES5的回调函数写法是一样的。还是推荐then,catch的写法,更加的清晰。
promise.then(
(res) => {
console.log("成功:", res);
},
(err) => {
console.log("失败:", err);
}
);
then的多次监听
虽然resolve不会执行多次,但是then是可以在多个地方监听的。
promise
.then((res) => {
console.log("成功:", res);
})
.catch((err) => {
console.log("失败:", err);
});
promise
.then((res) => {
console.log("成功:", res);
})
.catch((err) => {
console.log("失败:", err);
});
catch的细节
catch是可以单独监听的,并且可以多出监听,但是如果如果只有then,没有catch的话,会报没有监听catch的错误。
const promise = new Promise((resolve, reject) => {
// resolve("success")
reject("failure");
});
promise
.then((res) => {
console.log("成功:", res);
})
promise.catch((err) => {
console.log("失败:", err);
});
promise.catch((err) => {
console.log("失败:", err);
});
then的链式调用
then是可以链式调用的,因为then返回一个新的promise。
const promise = new Promise((resolve, reject) => {
resolve("success");
//reject("failure");
});
promise
.then((res) => {
console.log("第一次调用:", res);
})
.then((res) => {
console.log("第二次调用:", res);
})
.then((res) => {
console.log("第三次调用:", res);
});
promise.then((res) => {
console.log("独立的then:", res);
});
从第二次调用开始,then的res就没有值了,输出undefined。
第一次调用: success
独立的then: success
第二次调用: undefined
第三次调用: undefined
从第二个then开始,res的值实际上是来自上一个then的返回值。
promise
.then((res) => {
console.log("第一次调用:", res);
return "来自第一次调用的结果"
})
.then((res) => {
console.log("第二次调用:", res);
})
.then((res) => {
console.log("第三次调用:", res);
});
可以看到,第二个then的res的值来自第一个then的返回值。
第一次调用: success
独立的then: success
第二次调用: 来自第一次调用的结果
第三次调用: undefined
这里要注意,res的值并不是直接放进来的,中间还是通过promise来操作resolve函数来实现的。
then链式调用返回新的promise
这种情况并不复杂。还是会逐层传递,通过输出就可以很好的观察到。
const promise = new Promise((resolve, reject) => {
resolve("success");
//reject("failure");
});
const p=new Promise((resolve,reject)=>{
resolve("来自新的promise")
})
promise
.then((res) => {
console.log("第一次调用:", res);
return p
})
.then((res) => {
console.log("第二次调用:", res);
})
.then((res) => {
console.log("第三次调用:", res);
});
promise.then((res) => {
console.log("独立的then:", res);
});
第一次调用: success
独立的then: success
第二次调用: 来自新的promise
第三次调用: undefined
then链式调用返回thenable
这种情况也并不复杂,只是一般不会这样写。还是会逐层传递,通过输出就可以很好的观察到。
const promise = new Promise((resolve, reject) => {
resolve("success");
//reject("failure");
});
promise
.then((res) => {
console.log("第一次调用:", res);
return {
then:function(resolve){
resolve("来自thenable的数据")
}
}
})
.then((res) => {
console.log("第二次调用:", res);
})
.then((res) => {
console.log("第三次调用:", res);
});
promise.then((res) => {
console.log("独立的then:", res);
});
第一次调用: success
独立的then: success
第二次调用: 来自thenable的数据
第三次调用: undefined