本文将简单的介绍一下Promise
以及promisify
的方法,不涉及底层源码细则,最多从 Promise A+ 规范角度进行解读。
Yoshi, 开始吧。
回调地狱
在认识Promise
之前,有必要知道什么是 回调地狱(Callback Hell),它还有一个显得很霸气却极为令所有JS开发者感到无比厌烦的名字—— 厄运金字塔(Pyramid of doom)。之所以先了解这些,这是因为Promise
本来就是为了避免在多重异步操作时产生深层次的嵌套回调提出的一种新型异步解决方案。
老样子,来个栗子
function load(src,cb){
let es = document.createElement('script')
es.src = src
document.body.append(es)
es.onload=()=>{cb()}
es.onerror=()=>{cb(new Error('failed to load '+src )) }
}
load('./t1.js', err=>{
if(err===undefined) {
f1(); // t1内部的函数
} else {
console.log(err.message);
}
})
上面的代码是很很常见的需求,随便添加一个js
,然后执行里面的内容。 但是也会需要加载多个脚本,同时执行一些函数:
load('./t1.js', err=>{
if(err===undefined) {
f1(); // t1内部的函数
load('./t2.js', err=>{
if(err===undefined) {
f2(); // t2内部的函数
load('./t3.js', err=>{
if(err===undefined) {
f3(); // t3内部的函数
} else {
console.log(err.message);
}
})
} else {
console.log(err.message);
}
})
} else {
console.log(err.message);
}
})
这样的多重操作,会随着嵌套层数变深逐渐失控,于是便形成了臭名昭著的厄运金字塔。为了解决这个问题,于是Promise
便应运而生——主要是将深层回调变成了链式调用,这样能更符合人类逻辑:
第一步:先让load
返回一个Promise
:
// 每个 load 返回一个 Promise 对象
// 每个Promise对象都有一个 then 方法。
function load(src){
let es = document.createElement('script')
es.src = src
document.body.append(es)
return new Promise((resolve, reject)=>{
es.onload=()=>{resolve('success '+ src)}
es.onerror=()=>{reject(new Error('failed to load '+src )) }
})
}
第二步: 链式调用:
load('./t1.js')
.then(str=>{
console.log(str);
f1();
return load('./t2.js') //这里返回一个Promise
}) // 直接就能用 then
.then(str=>{
console.log(str);
f2();
return load('./t3.js') // 再返回一个Promise
})
.then(str=>{
console.log(str);
f3();
})
.catch(er=>{
console.log(er.message);
})
是不是有一种……之后的舒爽感??
Promise
极其重要的特征是链式调用,每个load
函数返回一个Promise
,该Promise
在确定是异步加载成功还是失败之后就会被返回然后提供给下一个操作使用。 一个Promise
队列就像是使用同步代码来实现异步操作一样。
认识Promise
这里就引用 MDN 上的Promise
概念:
Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象
重要说明:
Javascript
中的Promise
也参考了Promise/A+
规范。Promise
中的概念还是 ** MDN** 比较全面,因此我会根据 MDN 和 Promise/A+ 的内容综合说明。- 嘛,其实
Promise
概念不如何重要,重要的是如何应用它。
正文开始。
Promise构造:
语法:
var p promise = new Promise(function(resolve, reject){ // executor })
参数:
- executor : 在新建一个
Promise
实例时,必须向 Promise构造函数 传递一个回调函数(名曰:executor
),在新建对象时会立即执行它。 返回值: - 一个被设置了状态的
Promise
,它可能会有value
或reason
。 亦或是该Promise
为pending
。
附注 I:关于executor
的resolve
和reject
- 它必须有两个回调函数:
resolve
和reject
,无论是resolve
还是reject
,这两个都是Promise
内部提供的。 - 当首次执行
resolve
时,Promise
状态就已经被固定为fulfilled
,并且会有一个固定的value
- 当首次执行
reject
时,Promise
状态就被固定为rejected
; 并且会有一个固定的reason
- 一旦
Promise
被确定, 之后不论是再执行resolve
、reject
亦或是抛出异常
,都将被忽略。换言之,当Promise
被确定为fulfilled
后,就无法再转换到rejected
,反之亦然。
- 既没有执行
resolve
也没有reject
或抛出异常,那么Promise
状态为pending
,此时它可能会被转换到fulfilled
或rejected
。
附注 II: 关于settled
:
- 一旦
Promise
被确定为fulfilled
或rejected
, 都可以说Promise
被settled
- 一个未被
settled
的Promise
将处于pending
状态。
注意: 现在最好是使用Firefox浏览器进行实验,因为Chrome对console
做了优化,结果可能和理论不一致。
综上所述,可以知道:
(一) Promise状态一经确定(settled
),就不会再二次改变:
let pr = new Promise((res,rej)=>{
res('im fulfilled')
console.log(11111);
res('im fullfiled two')
console.log(22222);
rej(new Error('im rejected'))
throw new Error('hei, im exception')
})
console.log('33333');
console.log(pr);
浏览器输出:
务必注意:
11111
、22222
、33333
是立即输出的,因为executor
是立即执行的。- 因为
executor
中没有异步操作, 所以Promise
会立即确定状态。 - 一个
executor
是可以多次调用resolve
或reject
的,只是第一次调用才会生效,其他全被忽略
let pr = new Promise((res,rej)=>{
rej('im rejected')
console.log(11111);
res('im fullfiled two') // 被忽略!!
console.log(22222); // 正常执行
})
pr.then(undefined,er=>{}) // 如果注释掉,它会抛出一个异常!!!
console.log('33333');
console.log(pr);
浏览器输出:
(二) Promise如果是异步确定状态的,那么在此之前处于pending
。
let pr = new Promise((res,rej)=>{
console.log(11111);
setTimeout(res,1000,'ha,ha,ha,ha');
console.log(22222);
setTimeout(res,500, 'en,en,en,en'); // 先执行,
})
console.log(3333);
console.log(pr); // pending
setTimeout(console.log, 1500, pr) // fulfilled
浏览器输出:
根据以上推论,如果Promise
不执行resolve
时,那么就永远无法被settled
,即会一直处于pending
状态。
(三) Promise是无法手动取消的
一个promise
返回后由GC
回收,并且在JS中没有提供关于对象的销毁方法,因此它会一直存在当当前宏任务结束。
最后
…… 知识点不够集中,太多概念就会堆积在一起,很容易混乱。 所以决定——把之后的内容放到下一篇,这样读取来能容易些。嘛,为了自己之后方便整理。
作者:Lazy_K
链接:https://juejin.im/post/5ed5b708e51d452f9c27cead