随便查查网上的 Promise 教程,多到数不胜数,然而我还是没有准确理解什么是 Promise 以及它的使用场景。所以打算自己写一下学习心得,用来查漏补缺。目标是写的简单直白,便于以后查阅反思。
什么是 Promise?
“Promise 是一个对象。” 对于这种解释,我不是很满意,毕竟在 JS 的世界中,万物皆可对象(这句话也是流传已久的bug,想知道具体的原因,请查阅 null 相关的知识)。我们通过 typeof Promise
可以得到 function
。这进一步说明,Promise 更是一个函数。同时来复习一下:JS 中,函数是一种特殊的对象,MDN 中也叫做头等对象(first-class)。所以,请不要简单解释为 Promise 是一个对象,这有点偷懒。
那么在认识到 Promise 是一个函数后,这个函数的本质,又是什么?答案是:构造函数。为什么这样理解?因为我们在使用 Promise 的时候,永远是 new Promise()
开始的。既然是构造函数,当我们创建一个 Promise 实例的时候,这个实例对象都有什么内容?来看看下面的代码:
var p = new Promise(function(resolve, reject){});
consoel.log(p);
// __proto__: Promise
// [[PromiseStatus]]: "pending"
// [[PromiseValue]]: undefined
console.log(p.__proto__)
// Promise {constructor: ƒ, then: ƒ, catch: ƒ, finally: ƒ, Symbol(Symbol.toStringTag): "Promise"}
我们得到的对象 p 上,这个对象也比较简单,一共只有三个东西:proto,PromiseStatus,PromiseValue。这三个值。__proto__
上则存在我们熟悉的 then
等方法,这就是链式调用的原理——通过返回 Promise 对象,调用其原型链上的方法实现。其余两个是内部变量,一个是记录内部状态,另一个记录了返回值。
Promise 的状态有三种:
- pending
- fulfilled/resolved (好讨厌这种名不正言不顺的,一种状态两种表示的东西)
- rejected
PromiseValue 记录的返回值也很简单。如果一个 promise 的状态是完成(resolved)那么,返回值就是 then(res => return res;)
里的 res
;如果是拒绝状态,那么则是 .catch()
里面的。值得一提的是,Promise 实例中的状态,永远是确定的,而且不可逆,也就是说一个已经 resolved 或者 rejected 的promise,不能转化成 pending。
为什么要有 Promise?
之前我们说到 Promise 是一个构造函数,是从 Promise 的类型来看问题。如果从应用层面来看待,那么 Promise 则是一套异步操作的处理机制。这种机制十分擅长解决回调地狱。来写一个在 jQuery 时代不可避免的回调场景:
$.ajax({
success: function(res) {
if(res) {
callAnotherFun(function(res) {
$.ajax({
success: secondResponse
})
});
}
}
})
改写成 Promise
function fetch() {
return new Promise(function(resolve) {
$.ajax({
success: function(res) {
resolve(res);
}
})
});
}
fetch().then(res => {
return callAnotherFun(function(res) {
return fetch()
})
}).then(res => {
secondResponse(res)
})
从上面的改写可以看出 Promise 将多层函数回调的嵌套
function a() {
b(function () {
c(function() {
d(function() {
...
})
})
})
}
改写成了
myPromise().then(res => {
a();
return myPromise();
}).then(res => {
b();
return myPromise();
}).then(res => {
c();
return myPromise();
}).then(red => {
d();
})
这种写法,统一了异步处理的风格,已经写入 ES6 的标准之中。其实关于这样的链式调用,我们仍然有优化的空间,这个优化操作就引入下面要讲的 async/await
的相关内容。
async\await 是什么?
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
我们可以将上一节的代码改写成 async/await 的形式,如下:
async function myPromise() { }
async function main() {
await myPromise(function() {a () });
await myPromise(function() {b () });
await myPromise(function() {c () });
await myPromise(function() {d () });
}
main();
从上图可以看出,async 提供了一种像同步风格一样来编写异步过程的代码的方式。这里要注意的是 await
一定是写在 async
里面的。顾名思义它是 async wait
异步等待的意思。
异步的使用场景?
那么结合到具体业务,我们应该如何使用。常见的异步场景有:
- 文件上传
- 图片加载
- 自动补全
- 用户事件
等等…… 下面我从一个小栗子来简单应用下,感受 Promise 和 async 结合的代码的可读性。