一、何为promise,我们为何要使用它?
由于JavaScript语言特性,所有程序都是单线程执行的。由于这个特性,JavaScript的一些浏览器事件、请求事件都是异步执行的,通过回调函数处理异步的结果。这是很常见的语法,但是在一些场景下,就会形成回调函数嵌套回调函数,有的情况甚至套用多层,形成了“回调地狱”,这样使得代码臃肿可读性差而且难以维护。
<!--一个地狱回调的例子,上一个函数的回调结果被下一个函数所依赖-->
const verifyUser = function(username, password, callback){
require.verifyUser(username, password, (error, userInfo) => {
if (error) {
callback(error)
}else{
require.getRoles(userInfo, (error, roles) => {
if (error){
callback(error)
}else {
require.logAccess(roles, (error) => {
if (error){
callback(error);
}else{
callback(null, userInfo, roles);
}
})
}
})
}
})
}
复制代码
为了解决这种问题,社区提出了一些解决方案,采用链式调用的方法,来解决异步回调,并在在ES6被统一成规范。可以说Promise 是异步编程的一种解决方案。
二、Promise的基本用法
基本用法
作为新的规范,promise采用更加直观也更加易读的方式解决回调嵌套。ES6规定,promise对象是一个构造函数,通过new关键字来生成实例。下面是promise的基本用法
<!--promise的基本用法-->
const promise = new Promise((resolve, reject) => {
// 异步操作的代码
if (success){
resolve(value);
} else {
reject(error);
}
});
复制代码
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,他们是JavaScript引擎提供的两个函数。异步操作有两种结果:成功或失败
- resolve函数在异步操作由pending状态(执行进行中)变为resolved(成功状态)时触发,传递操作成功后的结果;
- reject函数在异步操作从pending状态(执行进行中)变为rejected(失败状态)时触发,传递操作失败后的结果。
注意promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变
promise对象方法
Promise.prototype.then()
那么问题来了,刚才我们说到promise状态改变后出触发相应的函数,那么我们处理状态改变的代码要写在哪里呢? 没错,就是then()方法。then方法是定义在原型对象Promise.prototype上的,它的作用是为 Promise 实例添加状态改变时的回调函数then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
<!--promise then方法-->
const promise = new Promise((resolve, reject) => {
resolve('fulfilled'); // 状态由 pending => fulfilled
}).then(result => {
console.log(result); // 'fulfilled'
}, reason => {
})
复制代码
const promise = new Promise((resolve, reject) => {
reject('rejected '); // 状态由 pending => rejected
}).then(result => {
}, reason => {
console.log(reason); // 'rejected'
})
复制代码
上边说过,promise状态一旦修改就不能再变 只能由 pending => fulfilled或者 pending => rejected
promise采用链式调用,then()为 Promise 注册回调函数,参数为上一个任务的返回结果,所以链式调用里then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。
const promise = new Promise((resolve, reject) => {
resolve("success")
})
.then(result => {
return result
})
.then(result => {
console.log(result) // "success"
})
.catch(err => {})
复制代码
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,也就是异步操作发生错误时的回调函数,另外,then()方法里的回调函数发生错误也会被catch()捕获。
<!--promise catch方法-->
const promise = new Promise((resolve, reject) => {
throw new Error('err');
// 或者reject(new Error('err'));
}).then(result => {
console.log(result);
}).catch(err => {
// 处理前两个promise产生的错误
console.log(err)
})
复制代码
到这里,细心的同学会发现,既然我then()方法第二个参数可以用来抛出错误,干嘛还要用这个catch()方法。 其实还是有区别的,在链式操作里,任何promise抛出的同步或异步错误都可以被then()方法捕获,而reject则处理当前promise的错误。因此,建议不要在then方法里面定义 reject 状态的回调函数(即then的第二个参数),总是使用catch方法,这样也更接近同步的写法(try/catch)。
<!--promise catch方法-->
const promise = new Promise((resolve, reject) => {
// some code
})
// good
promise.then(result => {
// success
}).catch(err => {
// err
})
// not recommend
promise.then(result => {
//success
},err => {
//err
});
复制代码
Promise.prototype.finally()
finally()方法是在ES2018引入标准的,该方法表示promise无论什么状态,在执行完then()或者catch()方法后,最后都会执行finally()方法。
<!-- promise finally方法-->
const promise = new Promise((resolve, reject) => {})
.then(result => {})
.catch(err => {})
.finally(() => {})
复制代码
Promise.all()
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
<!-- promise.all方法-->
const promise1 = new Promise((resolve, reject) => {resolve("promise1")}),
promise2 = new Promise((resolve, reject) => {resolve("promise2")}),
promise3 = new Promise((resolve, reject) => {resolve("promise3")});
Promise.all([promise1,promise2,promise3]).then(data => {
console.log(data);
// ["promise1", "promise2", "promise3"] 结果顺序和promise实例数组顺序是一致的
}).catch(err => {
consolo.log(err)
});
复制代码
只有promise1、promise2、promise3的状态都变成fulfilled,Promise.all的状态才会变成fulfilled,此时promise1、promise2、promise3的返回值组成一个数组,传递给Promise.all的回调函数。
只要promise1、promise2、promise3之中有一个被rejected,Promise.all的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给Promise.all的回调函数
在做项目的时候我们经常会碰到一个页面要有多个请求,我们可以使用promise.all封装,便于请求管理。
类似的axios也有axios.all()方法处理并发请求
Promise.race()
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
<!-- promise.race方法-->
const promise = Promise.race([promise1, promise2, promise3]);
复制代码
上面代码中,只要promise1、promise2、promise3之中有一个实例率先改变状态,promise的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
三、Promise的应用
与axios结合
项目中我们经常会遇到需要根据业务将axios再封装的,比如请求拦截设置token以及Content-Type,响应拦截根据不同的状态码设置不同的响应。此外我们还可以将axios再封装
import axios from "./axios"
import qs from "qs";
export default {
get: function(url, params) {
return new Promise((resolve, reject) => {
axios.get(url, {params: params})
.then(res => {
resolve(res)
})
.catch(err => {
console.log(err)
})
})
},
post: function(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, qs.stringify(params))
.then(res => {
resolve(res);
})
.catch(err => {
console.log(err)
})
});
}
}
<!--使用 整个user模块的请求都在此文件管理-->
import require from "@/utils/require"
const user = {
userList() {
return require.post("/api.php", {}).then(res => res.result)
},
userInfo() {
return require.post("/api.php?&uid=20", {}).then(res => res.result)
},
...
}
export default user
复制代码
异步加载图片
用promise实现异步加载图片的例子
function loadImageAsync(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => {
resolve(image);
};
image.onerror = () => {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
const loadImage = loadImageAsync(url);
作者:纱窗下的猫