也许是太久没有关注 Javascript,直到最近发现很多地方都出现 Promise(/A+) 这个词,才意识到我似乎错过了什么。不仅是 Javascript,连 PHP 的 Guzzle 库都有 Promise。
意识到这可能是一个我必须知道的知识点,我打开 MDN 查询了 Promise 的定义和用法,嗯,定义和例子我都看懂了,但依旧扫除不了我对 Promise 存在的意义的蒙逼感…… 到底这玩意儿好在什么地方啊?
还好有谷歌对智商的加持,我大概搞明白 Promise 是解决什么问题的了,看了网上那么多不明觉厉的文章,最后还是自己的理解比较接地气。
要说 Promise 的意义,有个话题是绕不过去的,那就是异步代码执行。
不过说这个话题之前我们先用同步执行的代码举一个超级简单的栗子:假如我是厨师要做鸡汤,做汤的过程可以用以下伪代码表示:
var 活鸡 = {...};
var 死鸡 = 厨师.杀鸡(活鸡);
var 拔了毛的鸡 = 厨师.拔毛(死鸡);
var 处理好的鸡 = 厨师.处理内脏(拔了毛的鸡);
var 鸡汤 = 厨师.煮鸡汤(处理好的鸡);
alert(鸡汤 + '准备完毕');
栗子虽然糙但是逻辑没毛病。看起来流程也是很简洁。
然而,假如厨师的几个处理鸡的方法都是异步的,那代码就会变成这鸡毛样:
var 活鸡 = {...};
厨师.杀鸡(活鸡, (死鸡) => {
厨师.拔毛(死鸡, (拔了毛的鸡) => {
厨师.处理内脏(拔了毛的鸡, (处理好的鸡) => {
厨师.煮鸡汤(处理好的鸡, (鸡汤) => {
console.log(鸡汤 + '准备完毕');
});
});
});
});
大家应该都看出差别,因为异步调用必须采用『回调函数』这种方式来处理异步代码的执行结果,如果每一步异步处理又是依赖于前一次处理的结果,就会发现代码不断在回调函数里缩进以至于达成所谓『回调地狱』的成就(想想我们的 Ajax 异步调用)。
大家都不愿见『地狱』如同大家都不爱在 if/else 里嵌套又一个 if/else 一样,所以 Promise 就这么应运而生了。
关于 Promise 的定义和各种方法的定义这里我就不再说了,MDN 上说的很详细。这里我只说 Promise 的用法。因为异步代码无法直接用 return 来给方法返回结果,所以只能用回调函数的方式。但如果把异步调用的代码返回 Promise,那么写法就会变成类似这样:
厨师.杀鸡(活鸡) // 异步处理,但返回 Promise 对象
.then((死鸡) => { return 厨师.拔毛(死鸡); }) // 继续返回新 promise 对象
.then((拔了毛的鸡) => { return 厨师.处理内脏(拔了毛的鸡); }) // 继续……
.then((处理好的鸡) => { return 厨师.煮鸡汤(处理好的鸡); }) // 不要停……
.then((鸡汤) => { console.log('撒得一碗好' + 鸡汤); })
这样看是不是感觉又利落许多?
异步调用除了返回值的获取不如同步调用那么方便之外,异常也是类似,所以除了 then
方法之外还有获取异常的 catch
方法:
厨师.杀鸡(活鸡).then(...)
.catch((e) => { ... });
说了 Promise 的目的,再来说说 Promise 的用法。初看 Promise 的 API,说实话的确是容易绕晕。假如让我们自己实现 Promise,我们可以先简化一下,只实现 then 的 onFulfilled
function Promise2(func) {
this.then = callback => {
this.thenCallback = callback;
};
this.fulfill = res => {
this.thenCallback(res);
};
func(this.fulfill);
}
再结合调用的代码一起看
let p1 = new Promise2(resolve => {
setTimeout(() => {
resolve('OK'); // 一般来说 Promise 都调用 Ajax 这样的异步处理,这里用 setTimeout 来简单举例
}, 2000);
});
p1.then(resolved => {
console.log(resolved);
});
首先 Promise 的构造函数里输入的参数是一个匿名函数,而且是一个处理异步调用的匿名函数,此匿名函数接受一个参数,正好此参数又是另外一个匿名函数(我怎么感觉从『回调地狱』跳到到了『匿名函数调用地狱』的坑了……),而这『匿名函数里的匿名函数类型参数』正是异步函数得到最终返回结果时,处理此结果的函数。
其实大家记住上面的结论就可以驾驭 Promise 了,但我们可以再继续聊一些细节:上面的代码是将 then
接受的匿名函数类型的参数,通过内部一个叫 fullfill
的私有属性(也是一个匿名函数……)调用的,看起来像是多了调用 fulfill
函数这个步骤,难道不能跳过 fulfill
直接让 A 函数调用 B 函数么?的确不能。因为匿名函数 A 要求传入接受异步执行结果的匿名函数,而 Promise 对象创建的时候真正的结果处理匿名函数 B 还没有通过 then
方法注册到 Promise 对象里呢,只能事先在 Promise 类里定义好 fulfill
方法,将 fulfill
传给 A 函数,等真正异步代码执行完毕的时候,因为主线程上 B 函数也已经注册好了,当 A 调用 fulfill
的时候,fulfill
再委托真正的结果处理函数 B 来执行结果处理。
就酱。
Promise/A+ 到底应该怎么用? by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人