1、回调地狱的烦恼
谈到回调地狱,举个生活中的例子
有一天,小明去自助烤肉吃饭,由于自助餐不允许浪费,所有小明每次都把拿到的东西吃完
然后看有没有吃饱,如果没有吃饱,就继续拿餐,吃饱了就停止
上述场景,使用程序实现如下
typescript
const sendMessage = (food: string, onFulfilled: () => void, onRejected: () => void) => {
console.log(`烤肉店,去拿${food}...`);
console.log(`干饭中...`);
setTimeout(() => {
//模拟是否吃饱
if (Math.random() <= 0.1) {
console.log(`吃了${food}`);
onFulfilled();
} else {
console.log(`吃了${food}`);
onRejected();
}
}, 1000);
};
sendMessage(
"五花肉",
() => {
console.log("吃饱了...");
},
() => {
console.log("没吃饱..");
sendMessage(
"牛肉",
() => {
console.log("吃饱了...");
},
() => {
console.log("没吃饱..");
sendMessage(
"鸡翅",
() => {
console.log("吃饱了...");
},
() => {
console.log("没吃饱...");
console.log("没食物了...");
}
);
}
);
}
);
上述代码非常恶心,每一次'没吃饱'都需要在onRejected
中作回调处理,如果一直没吃饱,就会一直拿东西,一直在onRejected
中作回调处理,代码完全没法读了。
小结
回调地狱就是在回调函数里面再次嵌套回调函数
要解决这样的回调地狱问题,就需要promise
出马
2、Promise规范
Promise
是一套专门处理异步场景的规范,它能有效的避免回调地狱的产生,使异步代码更加清晰、简洁、统一
这套规范最早诞生于前端社区,规范名称为 Promise A+
该规范出现后立即得到了很多开发者的响应
Promise A+
规定:
- 所有的异步场景,都可以看作一个异步任务,每个异步任务,在js中应该表象为一个对象,该对象之为
promise
对象,也叫任务对象
- 每个任务对象,都应该有两个阶段,三个状态
它们之间有以下逻辑
任务总是从未决阶段变到已决阶段,无法逆行
任务总是从挂起状态变到完成或者失败状态,无法逆行
状态不能倒流,任务一旦完成或者失败,状态就固定下来,永远无法改变
- 挂起 -> 完成,称为 resolve; 挂起 -> 失败 称为reject。任务完成时,可能有一个相关数据;任务失败时,可能有一个失败原因
- 可以针对任务进行后续处理,针对完成状态的后续处理称为onFulfilled,针对失败的后续处理为onRejected
以上就是整个 Promise A+规范的模型
3、Promise API
ES6提供了一套API,实现了Promise A+ 规范
基本使用如下
javascript
const promise = new Promise((resolve, reject) => {
//任务具体执行流程,该函数会立即执行
//调用resolve(data),可将任务变为fulfilled状态,data为需要传递的相关数据
//调用reject(reason),可将任务变为rejected状态,reason为需要传递的原因
});
promise.then(
(data) => {
// onFulfilled 函数,当任务完成后自动运行该函数,data为任务完成时的相关数据
},
(reason) => {
// onRejected 函数 ,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
}
);
通过Promise
改造上面代码
typescript
const sendMessage = (food: string) => {
return new Promise((resolve, reject) => {
console.log(`烤肉店,去拿${food}...`);
console.log(`干饭中...`);
setTimeout(() => {
//模拟是否吃饱
if (Math.random() <= 0.1) {
resolve(`吃了${food},吃饱了`);
} else {
reject(`吃了${food},没吃饱`);
}
}, 1000);
});
};
sendMessage("五花肉").then(
(res) => {
console.log(res);
},
(error) => {
console.log(error);
}
);
上面代码还是很恶心,没有解决回掉地狱问题,不着急,接着往下学习
补充一个API catch
.catch(onRejected)
= .then(null,onRejected)
4、链式调用
-
then
方法必定会返回一个新的Promise
,可理解为后续处理也是一个Promise
任务 -
新任务的状态取决于后续处理
若没有相关后续处理,新任务状态和前任务一致,数据为前任务的数据
若有后续处理但还未执行,新任务挂起
若后续处理执行了,则根据后续处理的情况确定新任务的状态
后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
后续处理执行有错,新任务的状态为失败,数据为异常对象
后续执行后返回的是一个任务对象,新任务的状态和数据对象与该任务一致
由于链式调用任务的存在,异步代码拥有了更强的表达力
arduino
// 常见任务处理代码
/*
* 任务成功,执行处理1,失败执行处理2
*/
// promise.then(处理1).catch(处理2);
// promise.then(处理1).catch(处理2);
/*
* 任务成功,依次执行处理1,处理2,若任务失败或者前面的处理有错,执行处理3
*/
// promise.then(处理1).then(处理2).catch(处理3);
有了链试调用,上述案例代码继续优化
javascript
const sendMessage = (food: string) => {
return new Promise((resolve, reject) => {
console.log(`烤肉店,去拿${food}...`);
console.log(`干饭中...`);
setTimeout(() => {
//模拟是否吃饱
if (Math.random() <= 0.1) {
resolve(`吃了${food},吃饱了`);
} else {
reject(`吃了${food},没吃饱`);
}
}, 1000);
});
};
sendMessage("五花肉")
.catch((error) => {
console.log(error);
return sendMessage("牛肉");
})
.catch((error) => {
console.log("鸡翅");
return sendMessage("鸡翅");
})
.then(
(res) => {
console.log(res);
},
(error) => {
console.log(error, "没吃的了");
}
);
终于通过链式调用解决回掉地狱问题了。
5、Promise的静态方法
好了,小明吃饭的问题解决了,吃完饭回家后,又出现新的问题了
小明的媳妇给小明交代了两个任务
-
洗衣服(交给洗衣机完成)
-
拖地(交给扫地机器人完成)
小明需要在所有任务完成之后汇到,哪些做好了,哪些没有做好。
为了最大程度的节约时间,小明希望这些任务同时进行,最终一起汇报结果统一处理
每个任务可以看做是一个返回Promise
的函数
javascript
const wash = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了洗衣机");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("衣服洗好了");
} else {
rejected("忘记加水了,把衣服整烂了");
}
}, 1000);
});
};
const sweep = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了扫地机器人");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("地扫好了");
} else {
rejected("机器人坏了,没有扫地");
}
}, 2000);
});
};
串行执行,先洗衣服,衣服洗完之后在扫地
javascript
const wash = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了洗衣机");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("衣服洗好了");
} else {
rejected("忘记加水了,把衣服整烂了");
}
}, 1000);
});
};
const sweep = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了扫地机器人");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("地扫好了");
} else {
rejected("机器人坏了,没有扫地");
}
}, 2000);
});
};
wash().then(() => {
sweep();
});
同时执行,洗衣服,扫地
javascript
const wash = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了洗衣机");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("衣服洗好了");
} else {
rejected("忘记加水了,把衣服整烂了");
}
}, 1000);
});
};
const sweep = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了扫地机器人");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("地扫好了");
} else {
rejected("机器人坏了,没有扫地");
}
}, 2000);
});
};
wash();
sweep();
并行执行有一个严重的问题,没法办汇总任务状态。
针对此问题,Promise提供了静态方法
Promise.resolve(data)
直接返回一个完成状态的任务
Promise.reject(reason)
直接返回一个拒绝状态的任务
上面两种语法没啥特殊的等于以下写法
javascript
const promise = new Promise((resolve,reject)=>{
resolve("成功")
})
const promise = new Promise((resolve,reject)=>{
reject("失败")
})
Promise.all(任务数组)
直接返回一个任务,任务数组全部成功则成功,任何一个失败则失败
Promise.any(任务数组)
返回一个任务,任务数组任一成功则成功,任务全部失败则失败
代码理解
javascript
const promise = Promise.all([Promise.resolve("成功"), Promise.reject("失败")]);
//这是一个失败状态
promise.then(null, (error) => {
console.log(error);
});
const promise = Promise.any([Promise.resolve("成功"), Promise.reject("失败")]);
//这是一个成功的状态
promise.then(()=>{
console.log("成功了")
});
Promise.allSettled(任务数组)
返回一个任务,任务数组全部已决则成功,该任务不会失败
只有状态都从pending状态改变才会执行then。
javascript
const pro1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功了1");
}, 2000);
});
const pro2 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("失败了1");
}, 4000);
});
const promise = Promise.allSettled([pro1(), pro2()]);
//这是一个失败状态
promise.then(
(data) => {
console.log(data, "成功了");
},
(error) => {
console.log(error);
}
);
Promise.race(任务数组)
返回一个任务,任务数组任一已决则已决,状态和其一致
只要状态有一个从pending状态改变就会执行then。
javascript
const pro1 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功了1");
}, 2000);
});
const pro2 = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("失败了1");
}, 4000);
});
const promise = Promise.race([pro1(), pro2()]);
//这是一个失败状态
promise.then(
(data) => {
console.log(data, "成功了");
},
(error) => {
console.log(error);
}
);
理解了Promise静态方法,allSettled最适合上面案例的使用场景
小明做家务,进行代码优化
javascript
const wash = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了洗衣机");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("衣服洗好了");
} else {
rejected("忘记加水了,把衣服整烂了");
}
}, 1000);
});
};
const sweep = () => {
return new Promise((resolve, rejected) => {
console.log("小明打开了扫地机器人");
setTimeout(() => {
if (Math.random() < 0.5) {
resolve("地扫好了");
} else {
rejected("机器人坏了,没有扫地");
}
}, 2000);
});
};
Promise.allSettled([wash(), sweep()]).then((data) => {
console.log(data);
});
由于promise
通过链式调用消除了回调地狱问题
官方就推出了新的关键字,更加优雅的表达promise
6、回调消除 Async 和 Await
async
async
关键字用于修饰函数,被它修饰的函数,一定返回Promise
javascript
const method1 = async () => {
return 1; //该函数的返回值是Promise完成后的数据
};
method1(); //Promise {1}
const method2 = async () => {
return Promise.resolve(1); //若返回的是Promise,则method得到的Promise状态和其一致,简单理解就是等于method2去掉async关键字
};
method2(); //Promise {1}
const method3 = async () => {
throw new Error("1"); //若执行过程报错,则任务是rejected
};
method3(); //Promise{<rejected> Error(1)}
await
await
关键字表示等待某个Promise
完成,它必须用于async
函数中
javascript
async function method() {
const n = await Promise.resolve(1);
console.log(n);
}
//上面的函数等于
function method1() {
return new Promise((resolve, reject) => {
Promise.resolve(1).then((n) => {
console.log(n);
resolve(1);
});
});
}
注意
await
必须在async
函数中使用
以上都是有关于Primise的所有知识了。
7、手写Promise
在手写promise
之前,先简单介绍一下为什么要手写promise
目前针对浏览器,Promise
的支持是非常好的,即便是有些浏览器不支持Promise
,也可以通过工程化做兼容处理,平时开发中不需要手写。但是在一些大厂面试时考察评率很高。
不仅是面试需要,作为一名高级工程师,还需要一个强的逻辑思维能力,把想的变成代码实现。
本篇文章手写Promise
,主要的就是实现Promise A+
规范
说明
本篇文章不会百分之百还原Promise A+
规范,但是会还原它核心的百分之八九十
实现状态的变化
由于Promise是一个构造函数,所以选用es6中calss来实现
typescript
//原始Promise
const promise = new Promise((resolve, reject) => {});
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
state: string;
value: undefined;
/**
* 创建Promise
* @param {Function} executor 任务的执行器,立即执行 === (resolve, reject) => {}
*/
constructor(executor: any) {
this.state = PENDING;
this.value = undefined;
//函数执行错误,promise直接变成reject状态
try {
executor(this.#resolve.bind(this), this.#reject.bind(this));
} catch (error) {
this.#reject(error);
}
}
#changeState(newState: string, value: any) {
//由于promise的状态不能随便更改,加一个判断
if (this.state !== PENDING) return;
this.state = newState;
this.value = value;
}
/**
* 标记当前任务完成
* @param {any} data 任务完成时的相关参数
*/
#resolve(data: any) {
//改变状态和数据
this.#changeState(FULFILLED, data);
}
/**
* 标记当前任务失败
* @param {any} reason 任务失败的相关数据
*/
#reject(reason: any) {
//改变状态和数据
this.#changeState(REJECTED, reason);
}
}
const myPromise = new MyPromise((resolve, reject) => {
resolve("成功数据");
reject("失败数据");
});
console.log(myPromise);
实现then函数
思考then函数的特点
-
接收两个参数 onFulfilled和onRejected
-
函数返回一个新的Promise
-
then函数中onFulfilled和onRejected执行时机。在微任务队列中执行
代码实现
typescript
//实现一个微任务队列
/**
* 把传递的函数放在微队列中
* @param {Function} callback 回掉函数
*/
const runMicroTask = (callback: any) => {
//判断node环境,nextTick就是node环境中的微队列api
if (!!process?.nextTick) {
process.nextTick(callback);
}
//浏览器环境通过 MutationObserver 来加入微队列 MutationObserver可以异步监听dom结点的变化
else if (MutationObserver) {
const p = document.createElement("p");
const observer = new MutationObserver(callback);
observer.observe(p, { childList: true });
p.innerHTML = "promise mock";
} else {
setTimeout(callback);
}
};
//新增then方法
then(onFulfilled: any, onRejected: any) {
return new MyPromise((resolve: any, reject: any) => {});
}
执行队列
分析Promise中then函数的执行时机,发现then函数并不会马上执行,甚至都不会马上放到微队列。它一定是等状态确定后才会执行
then作用实际上是把传入的函数参数放入一个队列,then函数每调一次就把callback放入任务队列中。
当状态改变之后,再从队列中取出callback,依次执行一遍
代码实现
typescript
//新增 任务队列
#handlers: { executor: any; state: string; resolve: any; reject: any }[] = [];
//加入任务队列函数
#pushHandler(executor: any, state: string, resolve: any, reject: any) {
this.#handlers.push({ executor, state, resolve, reject });
}
then(onFulfilled: any, onRejected: any) {
return new MyPromise((resolve: any, reject: any) => {
this.#pushHandler(onFulfilled, FULFILLED, resolve, reject);
this.#pushHandler(onRejected, REJECTED, resolve, reject);
});
}
遍历执行队列
- 遍历任务队列
kotlin
/**
* 根据实际情况执行队列,再状态改变的地方执行
*/
#runHandlers() {
if (this.state === PENDING) {
//目前任务任然挂起
return;
}
while (this.#handlers[0]) {
this.#runOneHandler(this.#handlers[0]);
this.#handlers.shift();
//任务必须处理一个删掉一个,不然异步任务会出现多次处理情况
}
}
/**
*
* @param {Object} handler
*/
#runOneHandler(handler: any) {}
- 确定任务执行时机
typescript
then(onFulfilled: any, onRejected: any) {
return new MyPromise((resolve: any, reject: any) => {
this.#pushHandler(onFulfilled, FULFILLED, resolve, reject);
this.#pushHandler(onRejected, REJECTED, resolve, reject);
this.#runHandlers(); //执行then函数调用之前就改变的状态的任务
});
}
#changeState(newState: string, value: any) {
//由于promise的状态不能随便更改,加一个判断
if (this.state !== PENDING) return;
this.state = newState;
this.value = value;
this.#runHandlers(); //状态变化执行队列
}
完成核心代码运行处理函数
直接上代码
typescript
const isPromise = (obj: Record<string, any>) => {
return !!obj && typeof obj === "object" && typeof obj.then === "function";
};
/**
*
* @param {Object} handler
*/
#runOneHandler({ executor, state, resolve, reject }: any) {
runMicroTask(() => {
if (this.state !== state) return; //状态不一致,不做任何处理
if (typeof executor !== "function") {
//如果传入的不是一个函数
//这里不太好理解,当传入的executor没有处理函数的时候,下一次的promise要和上一次的promise状态保持完全一致
this.state === FULFILLED ? resolve(this.value) : reject(this.value);
return;
}
//当前任务状态一致且executor是一个函数
try {
const result = executor(this.value);
if (isPromise(result)) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
});
}
最后完整代码
kotlin
//原始Promise
const promise = new Promise((resolve, reject) => {}).then(
(data) => {},
(reason) => {}
);
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const isPromise = (obj: Record<string, any>) => {
return !!obj && typeof obj === "object" && typeof obj.then === "function";
};
/**
* 把传递的函数放在微队列中
* @param {Function} callback 回掉函数
*/
const runMicroTask = (callback: any) => {
//判断node环境,nextTick就是node环境中的微队列api
if (!!process?.nextTick) {
process.nextTick(callback);
}
//浏览器环境通过 MutationObserver 来加入微队列 MutationObserver可以异步监听dom结点的变化
else if (MutationObserver) {
const p = document.createElement("p");
const observer = new MutationObserver(callback);
observer.observe(p, { childList: true });
p.innerHTML = "promise mock";
} else {
setTimeout(callback);
}
};
class MyPromise {
state: string;
value: undefined;
#handlers: { executor: any; state: string; resolve: any; reject: any }[] = [];
// 处理函数存放的队列,数据类型必须是一个对象,不然没法区分失败和成功的处理函数
//参数说明 executor:then的回掉函数,state:回掉函数中data和reason处理状态标识
//resolve data处理函数 reject:reason处理函数
/**
* 创建Promise
* @param {Function} executor 任务的执行器,立即执行 === (resolve, reject) => {}
*/
constructor(executor: any) {
this.state = PENDING;
this.value = undefined;
//函数执行错误,promise直接变成reject状态
try {
executor(this.#resolve.bind(this), this.#reject.bind(this));
} catch (error) {
this.#reject(error);
}
}
#changeState(newState: string, value: any) {
//由于promise的状态不能随便更改,加一个判断
if (this.state !== PENDING) return;
this.state = newState;
this.value = value;
this.#runHandlers(); //状态变化执行队列
}
/**
*
* @param executor 处理函数
* @param state //处理函数执行状态
*/
#pushHandler(executor: any, state: string, resolve: any, reject: any) {
this.#handlers.push({ executor, state, resolve, reject });
}
/**
* 根据实际情况执行队列,再状态改变的地方执行
*/
#runHandlers() {
if (this.state === PENDING) {
//目前任务任然挂起
return;
}
while (this.#handlers[0]) {
this.#runOneHandler(this.#handlers[0]);
this.#handlers.shift();
//任务必须处理一个删掉一个,不然异步任务会出现多次处理情况
}
}
/**
*
* @param {Object} handler
*/
#runOneHandler({ executor, state, resolve, reject }: any) {
runMicroTask(() => {
if (this.state !== state) return; //状态不一致,不做任何处理
if (typeof executor !== "function") {
//如果传入的不是一个函数
//这里不太好理解,当传入的executor没有处理函数的时候,下一次的promise要和上一次的promise状态保持完全一致
this.state === FULFILLED ? resolve(this.value) : reject(this.value);
return;
}
//当前任务状态一致且executor是一个函数
try {
const result = executor(this.value);
if (isPromise(result)) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
});
}
then(onFulfilled: any, onRejected: any) {
return new MyPromise((resolve: any, reject: any) => {
this.#pushHandler(onFulfilled, FULFILLED, resolve, reject);
this.#pushHandler(onRejected, REJECTED, resolve, reject);
this.#runHandlers(); //执行then函数调用之前就改变的状态的任务
});
}
/**
* 标记当前任务完成
* @param {any} data 任务完成时的相关参数
*/
#resolve(data: any) {
//改变状态和数据
this.#changeState(FULFILLED, data);
}
/**
* 标记当前任务失败
* @param {any} reason 任务失败的相关数据
*/
#reject(reason: any) {
//改变状态和数据
this.#changeState(REJECTED, reason);
}
}
const myPromise = new MyPromise((resolve, reject) => {
resolve("成功数据");
reject("失败数据");
});
console.log(myPromise);