@[toc]
小伙伴们知道,松哥最近在录 TienChin 项目,用的 RuoYi-Vue 脚手架,前端关于网络请求的地方,很多都涉及到了 Promise,为了让小伙伴们能更好的理解前端代码,咱们抽空整一篇文章和大家聊聊整个 Promise。
假设我现在有这样一个需求,用户先去登录,登录成功之后,再去服务端获取用户信息,获取到用户信息之后再去服务端获取前端的动态菜单。一般我们的 Ajax 请求都是异步形式,为了确保上一步操作成功再执行下一个请求,所以最终发出的请求伪代码类似下面这样:
$.ajax({ url:'/login', data:loginForm, success: function (data) { //登录成功 $.ajax({ url:'/getInfo', success: function (userInfo) { //获取用户信息成功 $.ajax({ url: '/getMenus', success: function (menus) { //获取菜单成功 } }) } }) } })
为了确保一个异步任务执行完成后,再执行下一个异步任务,我们不得不在回调函数中不停的写下去,上面我举的例子是三个请求嵌套,实际上可能会更多。。。这就是前端所谓的回调地狱。而 Promise 就是来解决回调地狱的。
Promise 从字面上理解,就是承诺,承诺将来在某一个时间会做某一件事。通过 Promise 我们可以将异步任务执行的代码和处理的代码完全分离开。
还是第一小节这个例子,如果我们用 Promise 写,那么方式如下:
function login(resolve, reject) { setTimeout(() => { let number = Math.random(); if (number > 0.5) { resolve("login success") } else { reject("login failed") } }, 2000); } function getInfo(resolve, reject) { setTimeout(() => { let number = Math.random(); if (number > 0.5) { resolve("getInfo success") } else { reject("getInfo failed") } }, 2000); } function getMenus(resolve, reject) { setTimeout(() => { let number = Math.random(); if (number > 0.5) { resolve("getMenus success") } else { reject("getMenus failed") } }, 2000); } new Promise(login).then(data => { console.log("login:", data); return new Promise(getInfo); }).then(data => { console.log("getInfo:", data); return new Promise(getMenus); }).then(data => { console.log("getMenus", data); }).catch(err => { console.log("err:", err); })
我们在 Promise 中写异步任务执行的代码,在上面的案例中,松哥通过 setTImeout 方法模拟了一个耗时操作,异步任务执行完毕后,我们调用 resolve 方法返回调用的结果(会进入到下一步的 then 中),也可以调用 reject 方法表示调用失败(会进入到 catch 中)。
then 中的返回值可以分为三种情况。
then 中方法的参数,是上一个 Promise 对象 resolve 的值,一个 Promise 对象可以有多个 then,例如上面案例的登录功能,我们可以一直 then 下去:
function login(resolve, reject) { setTimeout(() => { let number = Math.random(); if (number > 0.5) { resolve("login success") } else { reject("login failed") } }, 2000); } new Promise(login).then(data => { console.log("then1:", data); return data; }).then(data => { console.log("then2:", data); return data; }).then(data => { console.log("then3:", data); }).catch(err => { console.log("err:", err); })
这样可以一直返回,这有点像我们 Java 中的流式编程。
在 then 中,我们可以对返回结果进行判断,不满足条件也可以直接抛出异常,这样就会进入到最近的 catch 代码块中。如下案例:
function login(resolve, reject) { setTimeout(() => { let number = Math.random(); if (number > 0.5) { resolve("login success") } else { reject("login failed") } }, 2000); } new Promise(login).then(data => { console.log("then1:", data); throw new Error("出错啦"); }).then(data => { console.log("then2:", data); return data; }).then(data => { console.log("then3:", data); }).catch(err => { console.log("err:", err); })
像上面这段代码,如果进入到第一个 then 中,第一个 then 直接抛出异常,这样直接就进入到 catch 中了,后面的 then 就都不会执行了。
第三种情况就是 then 中也可以返回一个 Promise 对象,这个就如同我们第二小节的案例,我这里就不再赘述了。
就说一句,如果 then 中返回的是一个 Promise 对象,那么接下来的 then 其实是这个 Promise 对象的 then,而不是一开始的 Promise 的 then 了。例如如下伪代码:
A.then((data)=>{return B}).then(xxx)
假设 A 和 B 都是 Promise 对象,那么第二个 then 方法是 B 的 then。
好啦,这就是 then 中的三种返回值情况。
catch 主要是用来处理异常的情况,两种情况下会进入到 catch 中:
出了问题,就由最近的 catch 来处理。
最后还有一个 finally 用来兜底,这一套下来感觉有点像我们 Java 中的 try-catch-finally,也就是前面无论如何,最终 finally 中的代码都会执行。不过不同于 Java 中的 finally,Promise 中的 finally 在最终执行完毕后,还可以继续 then。。。前端的蜜汁操作。
最后,我们再来看看 Promise 中的其他静态方法。
Promise.all() 方法可以接收多个 Promise 对象,并且只返回一个 Promise 实例,这个方法会等所有输入的 Promise 对象的 resolve 方法都返回的时候,或者所有输入的 Promise 对象中有一个 reject 的时候,这个 all 就会执行结束,来看如下一个案例:
const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 3000, 'foo'); }); Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); }).catch(err=>{ console.log("err", err); })
当 promise1、promise2 以及 promise3 都执行了 resolve 的时候,就会进入到 then 中,这三个中有任意一个执行了 reject 就会进入到 catch 中。
Promise.race() 方法可以接收多个 Promise 对象,一旦迭代器中的某个 Promise resolve 或 reject,返回的 Promise 就会 resolve 或 reject。
和 all 方法的区别在于,race 方法是谁执行的快,就用谁的结果。我们来看如下一段代码:
const promise1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, 'one'); }); const promise2 = new Promise((resolve, reject) => { setTimeout(reject, 600, 'two'); }); Promise.race([promise1, promise2]).then((value) => { console.log(value); });
promise1 执行时间短,promise2 执行时间长,所以最终结果就是 promise1 的结果。
Promise.reject() 方法返回一个带有 reject 原因的 Promise 对象。来看如下一段代码:
function resolved(result) { console.log('Resolved'); } function rejected(result) { console.error(result); } Promise.reject("error").then(resolved).catch(rejected);
这个执行的时候就会进入到 catch 中。
Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 对象。
const promise1 = Promise.resolve(3); promise1.then(data=>{ console.log("data", data); },err=>{ console.log("err", err); })
这个用法比较简单,没啥好说的。
还有一些其他方法我就不一一列举了,感兴趣的小伙伴们可以看看 MDN 上的相关讲解:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise。
好啦,这个东西搞懂了,我们再去看 TienChin 项目的前端,就会非常容易了!