
粑粑超(公众号:前端悟道)
V1
2023/02/22阅读:40主题:前端之巅同款
js: 十分钟!坤坤陪你搞懂Promise
一、基本介绍
-
Promise 是承诺的意思,理解下来就是在一段时间内,等待一件事情的完成,在浏览器环境中,这个一段时间就是异步操作的表现。 -
区分同步和异步:当我们调用一个方法之后,需要等待这个操作返回结果,就是异步操作,否则为同步操作。
二、为什么需要Promise
1. 常见的异步操作
-
宏任务:setTimeout、ajax、 脚本 <script>
、MessageChannel(消息通道)、ui渲染、DOM事件 -
微任务:Promise.then(原生的Promise)、mutationObserver(h5提供的api)
2. 异步回调存在的问题
-
回调地狱、恶魔金字塔:当需要做多个异步操作时会导致代码多层嵌套,并且代码不整洁、不易阅读 -
并行结果:多个异步任务未分前后顺序执行时,需要等待任务都完成后才可继续执行后续逻辑时,无法并行执行任务节省时间
3. 关于 Promise 状态 🌿
这里我用🐔 你太美来介绍,方便大家记忆,将 Promise 当做一颗鸡蛋。 当坤坤还只是一颗蛋时,需要经历孵化的过程,他才能成为现在万众瞩目的爱坤
Promise 的状态一共有三种
-
pending:是正在孵化中 -
fulfilled:则是孵化成功了 -
rejected:则是孵化失败了

4. Promise 的注意事项 ⚠️
-
Promise 的结果只有 fulfilled 或者 rejected。蛋最后只有孵化成功或者孵化失败 -
当抛出异常时,会走到 rejected 的状态。蛋孵化的过程中夭折了,自然就孵化失败了 -
Promise 的状态一旦从 pending 更改后,就不可再改变。蛋孵化成小鸡后就不会再变成蛋了 -
new Promise 时,executor 函数会立即执行 -
resolve 和 reject 是两个函数,给用户使用,调用 resolve 状态变为成功,调用 reject 状态变为失败 -
每个 Promise 都需要一个 then 方法,then 接收两个回调函数,一个是成功回调一个是失败回调
三、Promise的基本使用
⚠️ 这里我会将平常使用的 promise 结构拆分,方便理解
1.导入自己实现的 promise
const Promise = require('./testPromise'); // 导入自己的promise
const executor = (resolve, reject) => {
setTimeout(e=>{
// 调用了成功的回调
resolve('小黑子露出鸡脚了吧~')
// 调用了失败的回调
// reject('小鸡子露出黑脚了吧~')
}, 10);
};
2. 构建 Promise 实例
构建后,在constructor中会立即执行 executor 函数
const promise1 = new Promise(executor);
3. then 中的成功和失败回调
const onFulfilled = data => {
console.log(data, '孵化成功了~')
};
const onRejected = err => {
console.log(err, '孵化失败了~')
}
4. 调用执行
promise1.then(onFulfilled, onRejected)
四、创建一个 Promise 类
⚠️ 这里的 executor 参数就是我们上面传入的方法,并且会立即执行
// 设置三个状态
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class Promise {
constructor(executor) {
// DDDD 在这里做逻辑处理
}
}
module.exports = Promise;
五、在Promise类中写入处理状态的逻辑
-
status
:默认状态为 pending -
value
:正确回调返回的值 -
errorReason
:失败回调的原因 -
resolvedCallbacks
: 存放 then 中成功回调的队列 -
rejectedCallbacks
: 存放 then 中失败回调的队列
⚠️ 注意事项:
-
用户传入的executor方法,可能会存在异常错误,这里需要 try Catch 处理,发生错误后直接进入
reject
,异常错误则作为失败原因 -
这里的
forEach(fn => fn())
作用是,当传入的executor为一个异步操作时,这时的状态会一直为pending,所以要先将成功的回调和失败的回调存起来,等待状态变更后,再循环执行,使用了发布-订阅模式 -
resolvePromise 方法相较复杂,放后边单独分析
// DDDD后期处理Promise使用,先在这打个标
function resolvePromise(promise2, x, resolve, reject) {};
// 实现Promise类
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = null;
this.errorReason = null;
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
// 用户调用的成功时的处理函数
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
this.resolvedCallbacks.forEach(fn => fn());
}
}
// 用户调用的失败时的处理函数
const reject = (error) => {
if (this.status === PENDING) {
this.errorReason = error
this.status = REJECTED
this.rejectedCallbacks.forEach(fn => fn());
}
}
// 当执行器出现异常时,直接进入reject
try {
executor(resolve, reject)
} catch (error) {
// 这个异常就作为失败的原因
reject(error)
}
}
then(onFulfilled, onRejected) { }
}
六、实现 then 方法
1. then方法代码
then(onFulfilled, onRejected) {
// 给onFulfilled和onRejected添加默认值
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected == 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
// 处理resolve状态的逻辑
if (this.status === FULFILLED) {};
// 处理reject状态的逻辑
if (this.status === REJECTED) {};
// 处理pending状态的逻辑
if (this.status === PENDING) {};
});
return promise2;
}
💡知识点
-
每次都reutrn一个Promise, 在then中不停的 new Promise实现链式调用
-
每个状态判断中,使用 setTimeout
做成异步操作,是为了确保能拿到promise2,若直接使用promise2
,上下文还没有执行完,promise2的结果为undefined
; -
每个状态判断中,若执行对应的回调时发生异常,使用 try Catch
捕获,并且执行promise2的reject
-
链式调用时,只要中其中一个 then的回调有返回值
,x就会有值,否则为undefined -
如果onFulfilled或者onRejected为 function
,则使用传入进来的方法,否则使用默认的 -
onFulfilled中的: v => v
相当于function (v) { return v}
-
onRejected中的: err => { throw err }
相当于function (err) { return throw err}

2. ✅处理fulfilled状态
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
};
3. ❎处理rejected状态
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.errorReason)
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
})
};
4. 💭处理pending状态
⚠️ 注意事项
-
Promise此时的状态还未确认
,在此状态中订阅,将成功回调存入到resolvedCallbacks
,失败回调都存入到rejectedCallbacks
-
这里需要存入一个 可执行的函数
到对应的数组中,函数的内容与其它两个判断的操作一样
if (this.status === PENDING) {
this.resolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
});
this.rejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.errorReason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
});
}
七、实现resolvePromise方法
1. promise2 === x的作用
-
主要是防止在then中return当前的promise2 -
下面这种使用,正常情况下返回的promise2不会成功也不会失败,加了 promise2 === x
判断让他直接走失败
function resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x){
const error = '[出现错误: 检测到Promise链接循环 #<Promise>]';
return reject(new TypeError(error));
}
}
// 调用例子
const promise2 = new Promise((resolve,reject)=>{
resolve('鸡你太美');
}).then(data=>{
return promise2;
});
2. 判断x是普通值还是Promise
-
因为所有Promise都是按照Promises/A+规范实现,所以两个不同的Promise实例对象是可以相互组合使用。 -
所以这里应该要判断x的类型是为 object
还是function
-
如果使用 x instanceof Promise
来做判断,不是同一实例的Promise则会被当做普通值进入resolve -
当x为普通值时,可 直接resolve(x)
function resolvePromise(promise2, x, resolve, reject) {
if(promise2 === x){
const error = '[出现错误: 检测到Promise链接循环 #<Promise>]';
return reject(new TypeError(error));
}
if(x !== null && ['object', 'function'].includes(typeof x)) {
// 处理当x为Promise时
}else{
// 普通值直接成功即可
resolve(x);
}
}
3. 当x不是普通值的时候
if(x !== null && ['object', 'function'].includes(typeof x)) {
let called = false;
try{
let then = x.then; // 取出then,下面使用
if(typeof then === 'function') {
// 返回的promise成功时调用
function success(data) {
if(called) return;
called = true;
// 递归解析直到是普通值为止
resolvePromise(promise2, data, resolve, reject);
};
// 返回失败时调用
function error(err) {
if(called) return;
called = true;
reject(err)
};
/**
* 直接采用上一次的取出来的then方法
* 使用x.then会重新取值,导致触发get
*/
then.call(x, success, error);
} else {
// 进来这,说明then就是一个对象而已
resolve(x);
}
} catch(e){
if(called) return;
called = true;
reject(e); // 取then的时候报错意味着出错了
}
} else {
resolve(x); // 普通值直接成功即可
}
4. 递归解析使用的场景
// 多层Promise的调用
new Promise((resolve, reject) => {
resolve();
}).then(() => {
return new Promise((resolve1, reject1) => {
setTimeout(() => {
const newP = new Promise((resolve2, reject2) => {
setTimeout(() => resolve2('🐔你太美'), 1000)
});
resolve1(newP);
}, 300)
})
}).then(data => {
console.log(data)
})
5. 其它Promise相互使用的场景
-
下面这种使用一个不是通过自己的Promise创建的 -
如果使用 x instanceof Promise
, 则会被当做普通值
// 使用一个不是通过new Promise创建的
let otherPromise = {
then(onFulfilled,onRejected){
onFulfilled()
}
}
let p = new Promise((resolve)=>{
resolve()
})
let p2 = p.then(()=>{
return otherPromise
})
p2.then((s)=>{
console.log('成功', s)
},(err)=>{
console.log('失败', err)
})
八、测试Promise,并通过Promises/A+规范的验证
1. 写入deferred
方法
默认测试的时候会调用此方法,会检测这个方法返回的对象是否符合规范,这个对象上需要有promise实例及
resolve
和reject
方法,在你完成的Promise.js中写入下面的方法
Promise.deferred = function () {
let dfd = {}
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
2. 安装依赖
npm install promises-aplus-tests -g
3. 运行命令开始测试
-
filename = 最终完成的Promise.js名称 -
然后就可以开心的测起来啦~
promises-aplus-tests 'filename'
4. 测试全部通过
九、总结
-
当x为object或者function时,还需要判断x.then的类型是否为 function
,x才有可能是一个Promise -
当x为object时,当做一个普通值进入 resolve(x)
-
called
是用来判断是否调用了resolvePromise
,调用过一次后就不再进入,防止重复调用 -
如果 resolve
的值一直是一个Promise,则一直递归解析,直到是普通值为止 -
其它的总结都写在了上面对应的地方 -
Promise其它的静态方法后面再单独写一篇

完整Promise代码
-
gitHub :https://github.com/babachao/javascript-promise -
码云 :https://gitee.com/huchaoMan/implement-promise
参考资料
-
[1] Promises/A+: https://promisesaplus.com/ -
[2] Promise-MDN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
公众号:前端悟道
作者介绍

粑粑超(公众号:前端悟道)
V1