js三座大山
一:函数式编程
js三座大山之函数1
js三座大山之函数2-作用域与动态this
二:面向对象编程
js三座大山之对象,继承,类,原型链
三:异步编程:
js三座大山之异步一单线程,event loope,宏任务&微任务
js三座大山之异步二异步方案
js三座大山之异步三promise本质
当前主流的js异步编程方案中 都是基于promise的。promise最大的优点就是统一了异步调用的api,所有异步操作都可以使用相同的模式来处理问题。使用链式调用,避免了回调地狱。本质上和回调的方案并没有区别。promise本质是一个存储回调的容器
和 js三座大山之异步二异步方案 中的基于事件的发布订阅
有点类似。
关于promise的基础使用请看
阮一峰promise
mdn-Promise
用法范式:
promise的用法可以分为两大步骤。
- 创建promise对象并传入执行器&同步运行执行器
ini
const execute = (onResolve, onReject) => {
setTimeout(() => {
onResolve(123);
}, 2000);
};
const p = new Promise(execute);
- 注册异步回调函数,等待执行器返回结果,调用注册的函数。
javascript
// 成功的回调
p.then((data)=>{
console.log('第一个成功的异步回调 异步结果:', data)
})
// 失败的回调
p.catch((reason)=>{
console.log('第一个失败的异步回调 异步结果:', reason)
})
// 成功|失败都被执行的回调
p.finally(()=>{
console.log('第一个finally异步回调')
})
容器结构:promise本质是一个存储回调的容器
那么这容器里面都有什么呐?

1.状态state
promsie内部维护了一个状态 可以从pendding->fulfilled || pendding->rejected。仅有这两种变化。且一旦状态改变,就不会再变,任何时候都可以得到这个结果。
这一点promise与基于事件的发布订阅不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
2.执行结果value
当执行器执行成功后需要设置结果 执行失败后需要设置失败原因 这都是运行的结果被保存在promise内部。
3.回调函数列表handles
有成功的回调函数 通过then,finally注册的 可以是多个
有失败的回调函数 通过then,catch,finally注册的 可以是多个
4.钩子函数onResolve/onReject
外部在promise构造函数中需要注入一个执行器 promise需要对外提供一个钩子 当执行器获取到异步结果后 需要通过钩子改变promise的状态 设置结果 触发回调函数执行。
5.api
例如接受注册回调函数的各种api then catch finally 以及一些静态race all resolve reject等。
promise特点
链式调用
promise的这些api 无论是静态的还是原型上的 都有一个特点即支持链式调用
。实现链式调用的原理也很简单,每次都返回一个新的promise实例
即可。
值穿透
promise的值通过钩子函数onResolve/onReject设置 或者通过注册的回调函数获取结果
,如果处理函数返回一个非Promise值,那么这个值会被直接传递到下一个
举个例子
例子1:
javascript
const execute = (onResolve, onReject) => {
setTimeout(() => {
onResolve(123);
}, 2000);
};
const p = new Promise(execute);
p.then(result => {
console.log('result:', result)
return 456;
}).catch(error => {
console.log('error:', error)
}).finally(() => {
console.log('finally')
}).then(res=>{
console.log('res:', res)
});
console.log('同步代码')

下面逐行分析下:
- js解析执行脚本代码,初始化执行器execute。
- 初始化第一个promise实例 将execute传入构造函数 execute被同步执行 遇到异步api计时器 加入到事件循环中
- p执行then 容器内部注册一个成功状态的回调函数 then执行完毕返回第二个promise。
- 第二个promise执行catch方法 内部被注册一个失败的回调函数 catch执行完毕 返回第三个promise实例。
- 第三个promise执行finally方法 内部注册成功和失败都要执行的回调函数 finally执行完毕 返回第四个promise实例。
- 第四个promise执行then方法 内部被注册一个成功的回调 返回第五个promise实例。
- 打印log 同步代码。 到这里同步代码执行完毕 实例化了5个promise。
下面是异步部分:
- 计时器运行结束 回调函数推入到事件队列 执行栈执行回调函数。
- resolve(123)钩子执行 触发第一个promise状态改变 pendding->fulfilled。value被设置为123。将value=123作为参数,依次执行容器内的成功回调函数。
- 成功回调函数执行结束 获取返回值456。触发第二个promise状态改变pendding->fulfilled,将第二个Promise的value设置为456。将value=456作为参数,依次执行第二个Promise内的成功回调函数。
- 第二个Promise内的成功回调函数为空。将value=456透传给第三个promise。同时第三个promise状态改变pendding->fulfilled。因为第三个promise内部的函数是通过finally注册的,所以在执行时并不会将value作为参数,仅仅是依次执行第三个Promise内的成功回调函数。
- 第三个Promise内的成功回调函数执行完毕,触发第四个promise状态改变pendding->fulfilled,同时将value=456透传给第四个promise. 将value=456作为参数,依次执行第四个Promise内的成功回调函数。
- 执行第四个Promise内的成功回调函数,获取返回值undefined,设置第五个promsie的value=undefined,触发第五个promise状态改变pendding->fulfilled。
promise值透传规律
- 通过
钩子设置onResolve/onReject
显示设置 例如上面的onResolve(123) - 如果
回调函数有返回值
则用当前的返回值设置下一个promsie的value 例如上面的返回值456 - 当没有对应的回调函数时(finally注册的相当于没有)透传当前的value。 例如第二个promise到第三个。
例子二:
javascript
const execute = (onResolve, onReject) => {
setTimeout(() => {
onReject('失败')
}, 2000);
};
const p = new Promise(execute);
p.then((data)=>{
console.log('成功的异步回调 异步结果:', data)
});
p.catch((reason)=>{
console.log('失败的异步回调 异步结果:', reason)
return 'fail'
});
p.finally(()=>{
console.log('finally异步回调')
});
问题:运行结果是什么
打印失败和成功的回调应该不难理解 因为promsie被reject了。
但是为什么还会有报错呐?我明明已经catch了啊~
分析下:

- 首先同步代码执行结束 生成了
4个promise实例
。第一个promise内部被注册了三组回调函数
。 - 计时器运行结束 onReject钩子触发
promise1
状态改变pendding->rejected
. promise的value='失败'。然后依次执行对应的回调列表。 - 执行第一个回调列表 发现没有失败对应的回调函数
所以透传当前的promise的值和状态给下一个promsie
。所以promise2
的状态改变pendding->rejected
. promise的value='失败'。 - 执行第二个回调列表 有失败的处理函数 将promsie的value='失败'作为参数 执行此函数。得到返回结果'fail'不是一个promise 因此将结果作为
promise3
的值 并更新状态pendding->fulfilled
. - 执行第三个回调列表 有失败的处理函数 但是这个函数是通过finally注册的 因此仅执行当前函数。并将当前promise的结果通过钩子onReject透传给
promsie4
。 所以第四个promsie状态pendding->rejected
,value='失败'。
- 第一次循环结束。
- 上次循环中3改变了
promise2
的状态为rejected,这次循环中依次执行对应的回调列表。因为promise2
没有回调列表 所以rejected状态,再次向上抛出。抛出第一个错误异常uncaught. - 上次循环中
promise3
状态为fulfilled,内部没有回调列表 所以不执行。 - 上次循环中
promise4
状态为rejected,依次执行对应的回调列表,因为promise4
没有回调列表 所以rejected状态,再次向上抛出。抛出第二个错误异常uncaught.
例子三
javascript
const execute = (resolve, reject) => {
setTimeout(() => {
resolve("123");
}, 2000);
};
const p = new Promise(execute);
p.then((data) => {
console.log("1111111");
})
.finally(() => {
console.log("33333333");
})
.finally(() => {
console.log("55555555");
});
p.finally(() => {
console.log("2222222");
}).finally(() => {
console.log("4444444");
});
输出结果:
1111111
2222222
33333333
4444444
55555555
代码实现:
手动实现了一版本promise,手写promise。有兴趣的同学可以看下~
kotlin
import microtask from './microtask.js'
const State = {
pending: "pending",
fulfilled: "fulfilled",
rejected: "rejected",
};
export class MyPromise {
#state = State.pending;
#res = null;
#handles = [];
constructor(executor) {
const resolve = (data) => {
this.#changeState(State.fulfilled, data);
};
const reject = (reason) => {
this.#changeState(State.rejected, reason);
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
#changeState = (state, res) => {
if (this.#state !== State.pending) {
return;
}
this.#state = state;
this.#res = res;
this.#runMicrotask();
};
#isPromiseLike = (promise) => {
return (
promise !== null &&
(typeof promise === "object" || typeof promise === "function") &&
typeof promise.then === "function"
);
};
#runOne = (callback, resolve, reject) => {
try {
const nextBack = this.#state === State.fulfilled ? resolve : reject;
if (typeof callback !== "function") {
nextBack(this.#res);
return;
}
const data = callback(this.#res);
if (this.#isPromiseLike(data)) {
data.then(resolve, reject);
return;
}
resolve(data);
} catch (error) {
reject(error);
}
};
#run = () => {
if (this.#state === State.pending || !this.#handles.length) {
return;
}
const item = this.#handles.shift();
const { onFulfilled, onRejected, resolve, reject } = item;
this.#runOne(
this.#state === State.fulfilled ? onFulfilled : onRejected,
resolve,
reject
);
this.#run();
};
/**
* 运行于微任务中
* */
#runMicrotask = () => {
microtask(this.#run);
};
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this.#handles.push({ resolve, reject, onFulfilled, onRejected });
this.#runMicrotask();
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
(data) => {
onFinally();
return data;
},
(reason) => {
onFinally();
throw reason;
}
);
}
}