Browse Source

finish

master
jingxun 2 years ago
parent
commit
41d2d93dbe
  1. 1
      code/ajax_promise.html
  2. 28
      code/index.html
  3. 21
      code/prmise/index.html
  4. 106
      code/prmise/js/promise.js
  5. 40
      code/promise_apis.html
  6. 117
      code/promise_class/promise.js
  7. 69
      note/lesson03_flow_of_promise.md
  8. 134
      note/lesson04_apis_of_promise.md
  9. 341
      note/lesson05_import_question_promise.md
  10. 465
      note/lesson06_encapsulation_of_promise.md
  11. 577
      note/lesson07_encapsulation_of_promise2.md

1
code/ajax_promise.html

@ -32,6 +32,7 @@
}
}
});
console.log(p);
p.then(
value => console.log(value),
reason => console.log(reason)

28
code/index.html

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
async function main() {
let p1 = Promise.resolve("OK");
let p2 = Promise.reject("Error");
let res1 = await p1;
console.log(res1);
try {
let res2 = await p2;
} catch (e) {
console.warn(e);
}
let res3 = await 123;
console.log(res3);
}
main();
</script>
</body>
</html>

21
code/prmise/index.html

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise Encapsulation</title>
<script src="./js/promise.js"></script>
</head>
<body>
<script>
let p = new Promise((resolve,reject)=>{
resolve("OK");
console.log(111);
});
p.then(v=>console.log(222));
console.log(333);
</script>
</body>
</html>

106
code/prmise/js/promise.js

@ -0,0 +1,106 @@
function Promise(excutor) {
this.PromiseState = "pending";
this.PromiseResult = null;
this.callbacks = [];
const resolve = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "fulfilled";
this.PromiseResult = data;
setTimeout(()=>this.callbacks.forEach(item => item.onResolved(this.PromiseResult)));
};
const reject = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "rejected";
this.PromiseResult = data;
setTimeout(()=>this.callbacks.forEach(item => item.onRejected(this.PromiseResult)));
};
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function (onResolved, onRejected) {
if (typeof onRejected !== "function") {
onRejected = reason => { throw reason; };
}
if (typeof onResolved !== "function") {
onResolved = value => value;
}
return new Promise((resolve, reject) => {
const callback = type => {
try {
let result = type(this.PromiseResult);
if (result instanceof Promise) {
result.then(
value => resolve(value),
reason => reject(reason)
)
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
};
if (this.PromiseState === "fulfilled") setTimeout(() => callback(onResolved));
if (this.PromiseState === "rejected") setTimeout(() => callback(onRejected));
if (this.PromiseState === "pending") {
this.callbacks.push({
onResolved: () => callback(onResolved),
onRejected: () => callback(onRejected)
});
}
});
};
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
}
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
v => resolve(v),
r => reject(r)
);
} else {
resolve(value);
}
});
}
Promise.reject = function (reason) {
return new Promise((resolve, reject) => reject(reason));
}
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let result = [];
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => {
count++;
result[i] = value;
if (count === promises.length) resolve(result);
},
reason => reject(reason)
);
}
});
}
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => resolve(value),
reason => reject(reason)
);
}
});
}

40
code/promise_apis.html

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise API</title>
</head>
<body>
<script>
// let p = new Promise((resolve, reject) => {
// reject("error");
// });
// p.catch(reason => console.log(reason));
// let p1 = Promise.resolve(123);
// let p2 = Promise.reject("error00");
// let p3 = Promise.reject("error");
// let p = Promise.all([p1, p2, p3]);
let p = new Promise(
(resolve, reject) => {
setTimeout(() => { resolve(111) }, 2000);
}
);
let p1 = new Promise(() => { });
console.log(p1);
let result = p.then(
value => {
console.log(value)
return new Promise(() => { });
}).then(
value => console.log(value)
).then(
value => console.log(value)
).catch(
reason => console.warn(reason)
);
</script>
</body>
</html>

117
code/promise_class/promise.js

@ -0,0 +1,117 @@
class Promise {
constructor(excutor) {
this.PromiseState = "pending";
this.PromiseResult = null;
this.callbacks = [];
const resolve = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "fulfilled";
this.PromiseResult = data;
setTimeout(() => this.callbacks.forEach(item => item.onResolved(this.PromiseResult)));
};
const reject = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "rejected";
this.PromiseResult = data;
setTimeout(() => this.callbacks.forEach(item => item.onRejected(this.PromiseResult)));
};
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onResolved, onRejected) {
if (typeof onRejected !== "function") {
onRejected = reason => { throw reason; };
}
if (typeof onResolved !== "function") {
onResolved = value => value;
}
return new Promise((resolve, reject) => {
const callback = type => {
try {
let result = type(this.PromiseResult);
if (result instanceof Promise) {
result.then(
value => resolve(value),
reason => reject(reason)
)
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
};
if (this.PromiseState === "fulfilled") setTimeout(() => callback(onResolved));
if (this.PromiseState === "rejected") setTimeout(() => callback(onRejected));
if (this.PromiseState === "pending") {
this.callbacks.push({
onResolved: () => callback(onResolved),
onRejected: () => callback(onRejected)
});
}
});
}
catch(onRejected) {
return this.then(undefined, onRejected);
}
static resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
v => resolve(v),
r => reject(r)
);
} else {
resolve(value);
}
});
}
static reject = function (reason) {
return new Promise((resolve, reject) => reject(reason));
}
static all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let result = [];
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => {
count++;
result[i] = value;
if (count === promises.length) resolve(result);
},
reason => reject(reason)
);
}
});
}
static race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => resolve(value),
reason => reject(reason)
);
}
});
}
}
let p = new Promise((resolve, reject) => {
setTimeout(() => resolve("I Promise!"), 1314);
});
p.then(
value => console.log(value),
reason => console.log(reason)
);

69
note/lesson03_flow_of_promise.md

@ -0,0 +1,69 @@
# `Promise`基本流程
上节课我们做了两个`Promise`的练习,一个是文件读取,另一个是`ajax`请求发送。但是我们也说了这么多了,那么`Promise`的工作流程到底是什么样的呢?那么我们接下来就来介绍一下。
## `Promise`的状态属性
我们之前说过`Promise`对象可以封装一个异步任务,在异步成功的时候调用`resolve`函数,在失败的时候调`reject`函数,而且这两个函数会把`Promise`对象的状态改成成功或失败。那么这个所谓的状态到底是个什么玩意儿呢?
其实这里我们所说的状态其实就是`Promise`对象的一个状态属性我们来看:
```js
const btn = document.getElementById("btn");
btn.addEventListener(
"click",
() => {
let p = new Promise(...);
console.log(p);
...
}
);
```
我们看之前的这段代码,我们打印一下我创建的`Promise`对象看一下:
![image-20220314093113249](https://file.lynchow.com/2022-03-14-013115.png)
我来看在`Promise`对象中有一个属性叫做`PromiseState`,这个就是我们一直在说的`Promise`对象的状态,那么这个属性有几种状态测存在呢?总共有一下三种:
- `pending` 未决定的
- `resolved` / `fulfilled` 成功
- `rejected` 失败
而当一个`Promise`对象在初始化的时候状态默认都是`pending`,其中`resolved`和`fulfilled`是同一个意思,只是名称变了一下,这两个是同一种状态。而状态改变只有两种可能:
- 从`pending`变成`resolved` / `fulfilled`
- 从`pending`变成`rejected`
不存在从`resolved` / `fulfilled`或者`rejected`变成`pending`,也不存在`resolved` / `fulfilled`与`rejected`之间相互转换。而且一个`Promise`对象只能改变一次状态。无论封装的异步任务成功还是失败,都会有一个结果数据,成功的结果数据通常称为`value`,而失败的结果数据通常称为`reason`。
## `Promise`结果属性
我们介绍完了`Promise`对象,我们来介绍`Promise`对象的另外一个属性,我们之前也一直在说,`Promise`封装异步任务,无论成功与否都会调用`resolve`和`reject`函数的其中一个,而且这两个函数都可以接收异步任务生成的结果值,那么这个结果值是什么呢?这也是`Promise`对象的一个属性。
![image-20220314095908222](https://file.lynchow.com/2022-03-14-015910.png)
我们来看一下,我们在打印出的`Promise`对象中有一个属性叫`PromiseResult`这就是我们所说的`Promise`对象的结果值。这个属性存放的就是`Promise`对象中异步任务成功或者失败的结果。而且这个属性也是一个对象,里面包含状态码,状态字符串,以及结果数据。
而这个属性是不允许手动修改的,只有`resolve`和`reject`函数可以修改,当通过这两个函数修改了`PromiseResult`属性之后,在后续的`then`方法中来直接取用这个属性中的值。
## `Promise`的工作流程
我们现在已经介绍完了`Promise`对象的属性,那么接下来我们来介绍一下`Promise`的工作流程吧
![未命名绘图](https://file.lynchow.com/2022-03-14-022819.png)
我们首先来看着个流程图首先我们要初始化一个`Promise`对象,当我们初始化了这个对象之后,这个对象的`PromiseState`属性默认为`pending`,紧接着开始执行异步任务,然后任务执行成功,则调用`resolve`函数,否则调用`reject`函数,然后我们就会获得一个`PromiseState`为`resolved`或者`rejected`的`Promise`对象。然后当我们调用`then`方法时,如果是`resolved`状态,则会调用第一个参数的函数,否则就会调第二个参数的函数,并返回一个新的`Promise`对象。
以上便是`Promise`的基本工作流程。
## 总结
- `Promise`的状态是指`Promise`对象的`PromiseState`属性
- `pending` 未决定的
- `resolved` / `fulfilled` 成功
- `rejected` 失败
- `Promise`的状态只能从`pending`转为`resolved`或者`rejected`
- `Promise`异步任务的结果值是`Promise`对象的`PromiseResult`属性
- `PromiseResult`属性只能由`resolve`或者`reject`函数来修改

134
note/lesson04_apis_of_promise.md

@ -0,0 +1,134 @@
# 如何使用`Promise`
上节课我们介绍了`Promise`的两大属性以及`Promise`的基本工作流程,那么这节课我们来说一说如何来使用`Promise`。
## `Promise API`
首先我们先来介绍一下`Promise`的一些`API`。那么`Promise`常见的`API`有哪些呢:
- `Promise`构造函数:`Promise(excutor){}`
- `Promise.prototype.then`方法:`(onResolved, onRejected) => {}`
- `Promise.prototype.catch`方法:`onRejected => {}`
- `Promise.resolve`方法:`value => {}`
- `Promise.reject`方法:`reason => {}`
- `Promise.all`方法:`promises => {}`
- `Promise.race`方法:`promises => {}`
### `Promise`构造函数
构造函数是我们最常用的一个`API`了,因为不用这个`API`我们怎么初始化`Promise`对象呢?所以这算是我们必用的一个`API`,那么这个`API`的结构是什么样子呢:
- `excutor`函数,`(resolve, reject) => {}`这个是`Promise`构造函数的参数,而且也是一个函数,可以用箭头函数也可以用匿名函数
- `resolve`函数,`value => {}`这是`excutor`函数的一个参数,由内部定义,供异步任务成功时调用
- `reject`函数,`reason => {}`这是`excutor`函数的另一个参数,由内部定义,供异步任务失败时调用
另外还有一点要说明一下:`excutor`函数会在`Promise`内部立即同步调用,异步任务在`excutor`中执行
### `Promise.prototype.then`方法
这个就是我们在实例化一个`Promise`对象之后要调用的`then`方法,我们知道`then`方法是用来指定回调的,这个方法的结构我们其实也比较熟悉了:
- `onResolved`函数:`value => {}`,这是当我们异步任务成功时调用的函数
- `onRejected`函数:`reason => {}`,这是当我们异步任务失败时调用的函数
而且这个方法会返回出一个新的`Promise`对象,当然我们并没有接触过,后面我们再对这方面来做相应的介绍。
### `Promise.prototype.catch`方法
这个方法我们目前还没用过。当然了这个方法也是用来指定回调的,但是这个方法只能指定失败的回调。我们来看一下它的结构:
- `onRejected`函数:`reason => {}`,异步任务失败时的回调函数
我们来演示一下:
```js
let p = new Promise((resolve, reject) => {
reject("error");
});
p.catch(reason => console.log(reason));
```
我们来看这段代码,我们实例化了一个`Promise`对象,并且直接执行了`reject`函数,那么`Promise`对象的状态就会被修改成`rejected`,然后我们再调用`catch`方法,并打印出我们传入的`reason`,我来看一下效果:
![image-20220314111517846](https://file.lynchow.com/2022-03-14-031519.png)
控制台中成功打印出了我们传入的`reason`其实`catch`方法是通过`then`方法来做了一个独立的封装,所以说`catch`方法也会返回一个新的`Promise`对象。
### `Promise.resolve`方法
这个方法和我们用过很多次的那个`Promise`中执行异步任务成功时调用的函数不是同一个函数,这个方法有点特殊,这个方法不属于`Promise`的实例对象。那么我们应该也能猜到,这个方法属于`Promise`函数对象自身方法,和类的静态方法类似。我们之前说的`then`方法以及`catch`方法这两个方法都属于`Promise`实例对象,和类方法类似,类方法存放在原型对象上供实例对象使用,这里也是一样。而`resolve`方法不一样。这个方法是属于`Promise`函数自身的,相当于静态方法。
那么这个方法有什么作用呢?我们先来看一下这个结构:`value => {}`
这个方法可以接收一个参数`value`,然后返回一个新的状态为成功或者失败的`Promise`对象,我们来演示一下:
```js
let p = Promise.resolve(123);
console.log(p);
```
我们调用`Promise.resolve`方法并且声明一个变量`p`来接收该方法的返回值,然后打印`p`看一下结果:
![image-20220314131121412](https://file.lynchow.com/2022-03-14-051123.png)
我们看一下,控制台中打印出了`p`是一个`Promise`对象,状态是成功的,而且值就是我们传入的值,我上面说过`resolve`方法会返回一个状态为成功或者失败的`Promise`对象,我们现在生成的是成功的。那么什么时候是失败的呢?
我们这里讲一下:
只要我们在调用`Promise.resolve`方法的时候传入的参数不是`Promise`对象,那么返回的新的`Promise`对象的状态就都是成功的,而且新的`Promise`的结果就是我们传入的值。
如果我们在调用`Promise.resolve`方法的时候传入的是一个`Promise`对象,那么返回的新的`Promise`对象的值就是我们传入的`Promise`的值,状态也是我们传入的`Promise`对象的状态。
我们来演示一下:
```js
let p = Promise.resolve(
new Promise(
(resolve, reject) => {
reject("Error")
}
)
);
console.log(p);
```
我们来看一下这段代码,我们在调用`Promise.resolve`方法是传入了一个失败的`Promise`对象,我们来看一下结果是什么样子:
![image-20220314133314574](https://file.lynchow.com/2022-03-14-053316.png)
我们看到控制台输出的是会有个失败的`Promise`对象,而且结果值是我们传入的`Promise`对象的结果值。但是下面还有一个报错是这么回事呢?因为我们这里有一个失败的`Promise`对象,而我们后续也没有指定相应的回调来处理这个失败的情况导致的报错。
以上就是`Promise.resolve`方法的介绍
### `Promise.reject`方法
这个方法和`Promise.resolve`方法一样,也相当于静态方法,而且也是用来快速生成一个新的`Promise`对象。但是这个方法只能生成状态为失败的`Promise`对象。这个方法的结构也很简单,`reason => {}`接收`reason`参数,该参数是失败的原因,返回一个失败的`Promise`对象。而且生成的`Promise`对象的结果值就是我们传入的值。
这里要注意的一点是,如果我们传入的是一个`Promise`对象,不论传入的这个对象是成功的还是失败的,我们生成的新的`Promise`对象都是失败的,而且结果是我们传入的`Promise`对象,而不再是`Promise`对象的结果值了。
### `Promise.all`方法
这个方法也相当于静态方法,这个方法的结构也比较简单:
`promises => {}`这个方法会接受一个参数,但是这个参数是一个数组,这个数组里面都是`Promise`对象,而这个方法的返回结果也是一个`Promise`对象,但是返回的`Promise`对象的状态就要由这个数组参数来决定了,只有当数组中所有的`Promise`对象都是成功的时候,返回的`Promise`对象才是成功的,否则返回的`Promise`对象就是失败的。
但是如果说数组中所有的`Promise`对象都是成功的,那么返回的`Promise`对象的结果是什么呢?如果返回的`Promise`对象是成功的,那么数组中所有的`Promise`的结果会组成一个新的数组,来作为返回的`Promise`对象的结果。如果返回的`Promise`对象是失败的,那么返回的`Promise`对象的结果就是数组中失败的`Promise`对象的结果。
我们要注意的一点是,如果是数组中有多个`Promise`对象都是失败的,那么返回的`Promise`对象的结果是数组中按顺序第一个失败的`Promise`对象的结果。
### `Promise.race`方法
这个方法和`Promise.all`方法类似,首先也相当于静态方法,其次结构一样,接受一个数组做参数,数组中都是`Promise`对象,并且返回一个新的`Promise`对象。
但是呢,这个数组中这么多`Promise`对象第一个完成的`Promise`对象的状态决定了`Promise.race`方法返回的`Promise`对象的状态。只要第一个完成的`Promise`对象是成功的那么的返回的新的`Promise`就是成功的。而且第一个完成的`Promise`的结果也就会是返回的`Promise`对象的结果。
以上便是我们`Promise`中常用的`API`

341
note/lesson05_import_question_promise.md

@ -0,0 +1,341 @@
# `Promise`关键问题
上节课我们介绍了`Promise`中的常用`API`,这节课我们来展开说一说`Promise`中的几个关键问题,这几个关键问题是为了后面封装方面的内容来做基础支撑的。
## 修改`Promise`状态
首先第一个问题,我们怎么修改`Promise`对象的状态?我在之前的课程中也已经说过了,我们不能够手动去修改`Promise`对象的状态,但是是不是没法修改呢?当然不是了。或许大家有人学过`Java`,知道`Java`中的属性有一种权限叫`private`,这个权限不允许我们手动去直接修改属性,但是我们会在类中预留一个方法,然后我们通过这个方法来去修改或者读取某个`private`的属性。
在`Promise`中也做了类似的设计。就是我们之前一直再用的`resolve`函数和`reject`函数。
- `resolve()`使得`Promise`对象状态从`pending` => `fulfilled`
- `reject()`使得`Promise`对象状态从`pending` => `rejected`
但是出了这两种方法还有没有其他方法来改变`Promise`对象的状态呢?其实是有的。我们来看一下:
```js
let p = new Promise(
(resolve, reject) => {
throw "Error";
}
);
console.log(p);
```
我们来看一下这段代码,我们在`Promise`中直接抛出一个异常,那么结果会是什么样子呢?我们来看一下:
![image-20220314143831856](https://file.lynchow.com/2022-03-14-063833.png)
我们看到控制台中输出的`Promise`对象的状态是`rejected`,而结果就是我们抛出的异常字符串。羡慕这个报错我们也说过,这是因为我们没有指定失败回调导致的。
所以说`Promise`对象的状态可以通过以上三种方法来修改。
## `Promise`能否执行多个回调
我们在初始化了`Promise`对象之后,都会来指定相应的回调函数,那么回调函数我们都是在哪指定的呢?我们上节课中在介绍到`then`方法和`catch`方法的时候就说到了,这两个方法是用于指定回调函数的,所以说我们的所有回调函数都是在`then`或者`catch`方法中来进行指定的。那么如果我给一个`Promise`对象指定了多个回调函数,那么这些回调函数会都被执行吗?我们来试一下:
```js
let p = new Promise(
(resolve, reject) => {
resolve();
}
);
p.then(()=>console.log("Callback 01"));
p.then(()=>console.log("Callback 02"));
```
我们现在这段代码是让这个`Promise`对象是一个成功的`Promise`,然后我们接下来指定了两个成功的回调,那么我们来看一下结果是怎样:
![image-20220314144853776](https://file.lynchow.com/2022-03-14-064856.png)
我们来看一下,控制台中成功输出了两个回调的执行结果,所以说当我们给一个`Promise`对象制定了多个回调的话,那么对应状态的回调都会被执行。
## `Promise`状态修改和指定回调的先后顺序
我接下来再来思考一个问题,我们在`Promise`对象的状态修改和回调指定到底谁先执行呢?我们通常通过`new Promise(excutor)`来实例化一个`Promise`对象,我们基本都是在`excutor`函数中执行异步任务,并调用`resolve`或者`reject`函数来修改状态,而我们是在实例化`Promise`对象之后再调用`then`或者`catch`来指定回调函数。
但是`Promise`对象的状态修改和指定回调的先后顺序真的就像我们上面所说的那样吗?有没有可能回调函数的指定在状态修改之前呢?其实是有可能的。而且先指定回调函数再修改状态,和先改变状态再指定回调函数这两种情况都是存在的。
既然两种情况都会存在那么什么时候会先改变状态,什么时候又会先指定回调呢?薛定谔的嘛?当然不是,当我们在`excutor`中直接调用`resolve`或者`reject`函数时这将是一个同步任务,这个时候会先改变状态。状态改变之后马上指定回调,然后根据状态调用回调函数获取到任务执行完成后的数据。
如果我们在`excutor`中开启一个异步任务的话,那么我们将会先指定回调函数,等异步任务执行完成后,状态修改,然后调用回调函数,获取到任务执行完后的数据。
## `then`方法返回的新`Promise`对象
我们之前提到过,`then`方法在被调用之后是会生成一个新的`Promise`对象的。但是这个新的`Promise`对象的状态还有结果都是由什么决定的呢?接下来我们就来展开说一说。
首先我来看一下代码:
```js
let p = new Promise(
(resolve, reject) => {
resolve();
}
);
let result = p.then(
() => console.log("Callback 01")
);
console.log(result);
```
我们这段代码干了什么?首先我们实例化了一个成功的`Promise`对象,然后在`then`方法中指定了成功时的回调函数,但是我的关注点不在这,我们关注点是我们声明一个变量来接收`then`方法的返回值,并打印出来。
![image-20220314152458276](https://file.lynchow.com/2022-03-14-072735.png)
我们来看,控制台输出了一个对象。这正如我们之前说的会返回一个新的`Promise`对象,但是这个对象的状态和结果都是什么呢?其实`then`方法返回的`Promise`对象状态和结果都由执行的回调函数来决定。
什么意思呢,就是说不管`then`方法中指定的成功的回调以及失败的回调哪一个被执行,那么被执行的回调函数的执行结果将决定`then`方法返回的`Promise`对象的状态和结果。
那么回调函数执行的结果有几种情况呢?
- 抛出异常
- 返回非`Promise`类型的结果
- 返回`Promise`对象
可能有人要说还有没有返回值的情况,但是我们都知道`js`函数没有返回值代表着返回`undefined`,这也属于非`Promise`类型的结果。所以说一个函数被执行之后结果只有以上三种情况。那么我们来分贝测试一下:
```js
let p = new Promise(
(resolve, reject) => {
resolve();
}
);
let result = p.then(
() => { throw "error"; }
);
console.log(result);
```
首先我们让回调函数抛出异常看一下结果:
![image-20220314153709494](https://file.lynchow.com/2022-03-14-073710.png)
生成的`Promise`对象是一个失败的`Promise`,而且结果值就是我们的异常字符串。
```js
let p = new Promise(
(resolve, reject) => {
resolve();
}
);
let result = p.then(
() => { return 123; }
);
console.log(result);
```
这一次我们让回调函数返回一个非`Promise`类型的数据看一下结果:
![image-20220314154046706](https://file.lynchow.com/2022-03-14-074048.png)
控制台中输出了一个成功的`Promise`对象,而`Promise`对象的结果就是我们的返回值。
```js
let p = new Promise(
(resolve, reject) => {
resolve();
}
);
let result = p.then(
() => {
return new Promise((resolve,reject)=>{
resolve("OK");
});
}
);
console.log(result);
```
这一次呢我们让回调函数返回一个`Promise`对象,这次返回的是一个成功的`Promise`,那我们来看一下结果:
![image-20220314164408909](https://file.lynchow.com/2022-03-14-084410.png)
生成的`Promise`对象也是一个成功的`Promise`,而且结果也是我们回调函数返回的`Promise`对象的结果值,那么如果我们的回调函数返回的是一个失败的`Promise`对象呢?
```js
let p = new Promise(
(resolve, reject) => {
resolve();
}
);
let result = p.then(
() => {
return new Promise((resolve,reject)=>{
reject("error");
});
}
);
console.log(result);
```
我这次再看一下结果:
![image-20220314164626012](https://file.lynchow.com/2022-03-14-084627.png)
这次`then`方法生成的`Promise`对象就是一个失败的`Promise`,而且结果也是回调函数返回的`Promise`对象的结果。
## `Promise`串联任务
这个词或许大家比较陌生,什么叫串联任务呢?我们之前不是说`then`方法会返回一个新的`Promise`对象嘛,那么这个新的`Promise`对象是不是也可以调用一个`then`方法?那么我们是不是直接链式调用像这样`p.then(function).then(function)...`就可以了?这一步操作就是我们所谓的串联任务。
那么我们来演示一下:
```js
let p = new Promise(
(resolve, reject) => {
resolve(123);
}
);
let result = p.then(
() => {
return new Promise((resolve, reject) => {
resolve("OK");
});
}).then(
value => console.log(value)
);
```
我们来看上面这一段代码,我们首先实例化一个`Promise`对象,这个对象里面可以执行同步任务也可以执行异步任务,然后再指定回调函数的时候,我们可以在回调中再开一个`Promise`来执行一个新的同步或者异步任务。当`then`方法调用完成之后,是不是获得了一个新的`Promise`对象,那么这个对象是不是可以再调用`then`方法?那么这个时候我们再在`then`方法中指定回调函数,这样的话就实现了`then`方法的链式调用,也就实现了串联任务,因为我们通过一次链式调用把两个任务给串联起来了。
![image-20220314170153227](https://file.lynchow.com/2022-03-14-090155.png)
我们来看一下,控制台中也成功输出了链式调用最后一节的回调的执行结果。
## `Promise`异常穿透
我们接下来来介绍另一个新的名词,叫异常穿透,那么什么叫做异常穿透呢?比如我们做了`then`方法的链式调用,但是谁都没法保证在整个调用链的任何一个环节都不会报错。既然可能报错,那我们肯定得捕获异常,但是每个环节都写一个异常捕获机制的话也太麻烦了,这样的话`Promise`的优势也没法体现出来,所以说我们可以用异常穿透来在整个调用链的最后一环来捕获异常:
```js
let p = new Promise(
(resolve, reject) => {
setTimeout(() => { resolve("OK") }, 2000);
}
);
let result = p.then(
value => {
return new Promise((resolve, reject) => {
console.log(value)
setTimeout(() => { reject("error") }, 100)
});
}).then(
value => console.log(value)
).then(
value => console.log(value)
).catch(
reason => console.warn(reason)
);
```
我们来看一下,我们在`then`链式调用的最后一环调用了`catch`方法,那么们来让整个调用链的其中一环失败来看一下结果:
![image-20220314171322877](https://file.lynchow.com/2022-03-14-091325.png)
我们看见我们是在第二环的异步任务失败了,然后打印出了失败的原因。那么我们如果是回调抛出异常了呢?
```js
let p = new Promise(
(resolve, reject) => {
setTimeout(() => { resolve("OK") }, 2000);
}
);
let result = p.then(
value => {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve("OK!") }, 100)
});
}).then(
value => { throw "error"; }
).then(
value => console.log(value)
).catch(
reason => console.warn(reason)
);
```
我们现在让调用链的第三环抛出异常再来看一下结果
![image-20220314171733291](https://file.lynchow.com/2022-03-14-091735.png)
这次我们看到控制台也成功不活了异常并执行了指定的回调。
## `Promise`链的中断
首先什么是`Promise`链?我们如果之前用了`then`方法的链式调用,就会形成一个调用的链状结构,这个链状结构就是我们所谓的`Promise`链。我们来看一下我们现在的代码:
```js
let p = new Promise(
(resolve, reject) => {
setTimeout(() => { resolve(111) }, 2000);
}
);
let result = p.then(
value => {
console.log(value)
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(222) }, 100)
});
}).then(
value => console.log(value)
).then(
value => console.log(value)
).catch(
reason => console.warn(reason)
);
```
现在我们这个`Promise`链现在输出的情况是什么呢?
![image-20220314172313728](https://file.lynchow.com/2022-03-14-092315.png)
是不是除了`catch`方法那一环以外,其他的都输出了,因为我们没有抛出异常也没有失败的`Promise`,所以`catch`方法并没有被调用。那么我们来看一下如果说我希望我们的`Promise`链仅仅只执行完第一环就中断的话,应该怎么处理呢?我们说过`then`方法被调用后会生成一个新的`Promise`对象,而这个`Promise`对象有几种状态?是不是三种?而`then`方法是不是只能指定`resolved`和`rejected`的回调函数?那么我们如果返回一个在这两个状态之外的一个`Promise`对象是不是在接下来的`then`方法中什么回调都匹配不上啊?那么这样的话,是不是就可以直接中断我们的`Promise`链了呢?我们来验证一下:
```js
let p = new Promise(
(resolve, reject) => {
setTimeout(() => { resolve(111) }, 2000);
}
);
let p1 = new Promise(() => {});
console.log(p1);
let result = p.then(
value => {
console.log(value)
return new Promise(() => {});
}).then(
value => console.log(value)
).then(
value => console.log(value)
).catch(
reason => console.warn(reason)
);
```
我们这一次在第一环中返回了一个从来没见过的一个`Promise`对象,我们是不知道这个`Promise`对象到底是什么状态,所以我们单独声明这样一个`Promise`对象来输出看一下:
![image-20220315084244116](https://file.lynchow.com/2022-03-15-004245.png)
首先我们看我们声明的这个`Promise`对象,它的状态是`pending`,那么在`then`方法中将会一个回调都匹配不到,所以就仅仅只执行了`Promise`链的第一环然后就中断了。
所以说中断一个`Promise`链的方法就是让`then`方法返回一个状态为`pending`的`Promise`对象。而且有且仅有这一种方法。
## 总结
以上便是`Promise`中的几个关键问题。
- 修改`Promise`对象的状态有 3 种方法
- `resolve`函数
- `reject`函数
- `throw`关键字
- `Promise`对象某状态如果指定了多个回调,那么当`Promise`对象变更为该状态时,这些回调都讲被执行
- `Promise`对象中如果封装的是同步任务,那么先改变状态,如果封装的是异步任务,则先指定回调
- `then`方法返回的新`Promise`对象分为三种情况
- 如果回调中抛出异常,`then`方法将返回失败的`Promise`对象,结果值为抛出的异常字符串
- 如果回调函数返回一个非`Promise`对象的数据,`then`方法返回一个成功的`Promise`对象,结果值为回调的返回值
- 如果回调函数返回一个`Promise`对象,`then`方法返回一个状态和结果值都与回调函数返回的`Promise`对象一致的新`Promise`对象
- `then`方法的链式调用就是任务的串联,因为`then`方法可以返回一个新的`Promise`对象,而这个新的`Promise`对象中可以开新的任务
- 在`then`方法的调用链的最后一环添加`catch`方法的调用可以捕获整条调用链中任何一环中的异常或者失败,这叫做异常穿透
- 可以在`then`方法的调用链中的任何一环中返回一个状态为`panding`的`Promise`对象来中断`Promise`链

465
note/lesson06_encapsulation_of_promise.md

@ -0,0 +1,465 @@
# `Promise`自定义封装(一)
上节课我们介绍了`Promise`中的一些关键问题,也说了这些问题是作为后续学习`Promise`封装的基础支撑的,那么接下来我们就开始学习一下关于`Promise`的自定义封装。所谓自定义封装其实就是手写`Promise`。用纯原生`js`代码来实现`Promise`。
## 初始结构搭建
我们想要手写一个`Promise`底层实现可想而知是一件比较困难的事情,所以我们把整个`Promise`来拆分开逐一介绍,首先第一步我们来从搭建初始结构开始。
```js
let p = new Promise(
(resolve, reject) => {
resolve("OK");
}
);
p.then(
value => console.log(value),
reason => console.log(reason)
);
```
我们来看一下上面这段代码是正常情况下我们来创建的一个`Promise`对象,这一段代码在控制台中执行肯定是输出`OK`,我们来看一下:
![image-20220315141248978](https://file.lynchow.com/2022-03-15-061250.png)
但是现在我们要自定义的话那肯定要把内置的`Promise`覆盖掉,那么我们来自定义一个`js`,并在我们的`html`文件中引入
```js
// promise.js
function Promise() {
}
```
首先,`Promise`是什么?我们在之前的内容中介绍过,`Promise`是一个构造函数,我们可以通过这个构造函数来实例化一个对象。那么我们现在就定义一个函数。
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Promise Encapsulation</title>
<script src="./js/promise.js"></script>
</head>
<body>
<script>
let p = new Promise(
(resolve, reject) => {
resolve("OK");
}
);
p.then(
value => console.log(value),
reason => console.log(reason)
);
</script>
</body>
</html>
```
然后我们在`html`文件中引入这个`js`文件,我们来看一下能不能成功覆盖内置的`Promise`构造函数:
![image-20220315141902394](https://file.lynchow.com/2022-03-15-061904.png)
控制台里面报错了,说`p.then`不是一个函数,这是为什么呢?这说明我们已经成功用我们自己定义的`Promise`函数覆盖了内置的`Promise`函数了,而我们自己定义的`Promise`函数就只是一个空的函数,也没有为我们自己定义的`Promise`函数添加`then`方法。所以说这里报错了,那么我们就给`Promise`函数添加一个`then`方法:
```js
function Promise(excutor) {
}
Promise.prototype.then = (onResolved, onRejected) => {
};
```
我们现在再来看代码,首先我们给`Promise`函数添加了一个形参,为什么要添加这个形参?因为我们在实例化`Promise`对象的时候要传入一个执行器函数来执行异步任务或者同步任务。其次我们给`Promise`的原型对象上添加了一个`then`方法,`then`方法是用来指定回调的,要接收两个函数类型的参数,所以我们也加了两个形参,现在我们再来看一下效果:
![image-20220315142735791](https://file.lynchow.com/2022-03-15-062737.png)
这次没有报错,但是控制台中也没有输出任何东西,因为我们虽然定义了`then`方法,但是我们并没有做任何操作,所以只是使语法上完成了初始结构的搭建。
以上便是一个`Promise`的最初始的一个基本结构。
## `resolve`与`reject`函数初始结构搭建
我们现在已经有了一个`Promise`的基本解构了,但是感觉好像还是还差点意思,我们在实例化`Promise`对象的时候是不是要传进去一个执行器函数(`excutor`)啊?而且我们之前在介绍的时候也说过我们的执行器函数在实例化`Promise`对象的时候会立即被同步调用,那么就简单了:
```js
function Promise(excutor) {
excutor();
}
```
我们直接这样同步调用一下不就行了嘛,当然这样的思路是对的,但是大家还记不记得我们传入的执行器函数都接收了两个形参?而且这两个形参都是函数,一个是`resolve`函数,另一个是`reject`函数。那么好,我们这样改一下代码:
```js
function Promise(excutor) {
excutor(resolve, reject);
}
```
但是这样能行吗?
![image-20220315143805460](https://file.lynchow.com/2022-03-15-063806.png)
直接报错了,说`resolve`没有被定义,那么我们是不是就要来声明一下这两个函数呢?我们来修改一下代码:
```js
function Promise(excutor) {
const resolve = data => { };
const reject = data => { };
excutor(resolve, reject);
}
```
大家来看这段代码,我们声明了两个箭头函数,分别是`resolve`和`reject`,而且这两个函数在声明的时候都分别接收了一个形参,因为我们在执行器函数中开启同步任务或者异步任务的时候都是要传入我们异步任务成功或者失败时候的值的,所以需要预留一个形参的位置。那么我们现在再看一下:
![image-20220315144258057](https://file.lynchow.com/2022-03-15-064259.png)
现在我们再看控制台里不再报错了,这样的话我们就完成了`resolve`函数和`reject`函数的结构搭建
## `resolve`函数与`reject`函数的实现
我们现在已经实现了`resolve`和`reject`的基本机构了,但是我们这两个函数存在的目的是什么呢?当然是用来修改`Promise`对象的状态和`Promise`对象的结果值,但是我们现在就一个空函数那么肯定是没有办法修改`Promise`对象的状态和结果值的。
那么接下来我们就来实现这两个函数:
```js
function Promise(excutor) {
this.PromiseState = "pending";
this.PromiseResult = null;
const resolve = data => {
this.PromiseState = "fulfilled";
this.PromiseResult = data;
};
const reject = data => {
this.PromiseState = "rejected";
this.PromiseResult = data;
};
excutor(resolve, reject);
}
```
我们来看这段代码,第一步我们先用`this`关键字给`Promise`添加了两个属性,分别就是`Promise`的状态和结果,为什么要添加这一步呢?因为我们`Promise`对象在第一步实例化的时候是不是有默认状态和结果的?默认是`pending`状态,结果是`null`,所以说我们要在第一步来添加默认状态和结果。
然后再来看`resolve`函数和`reject`函数,这两个函数接收一个形参,根据我们之前的学习都知道,这个形参就是`Promise`对象的结果值,而这两个函数对状态的修改是固定的,不需要参数传入,那么我们直接再通过`this`关键字将对应的属性做一下修改就可以完成了。
```js
let p1 = new Promise(
(resolve, reject) => {
resolve("OK");
}
);
let p2 = new Promise(
(resolve, reject) => {
reject("error");
}
);
console.log("p1: ", p1);
console.log("p2: ", p2);
```
但是我们现在还没有去实现`then`方法的内部代码,那么现在我们来打印一下看看我们的`resolve`函数和`reject`函数能不能正常工作:
![image-20220315151051520](https://file.lynchow.com/2022-03-15-071053.png)
我们可以看到控制台中正常输出了我们实例化的`Promise`对象。那么这样我们便成功实现了`resolve`函数和`reject`函数的内部代码逻辑。
## `throw`异常处理
我们在之前的内容中说过,修改`Promise`对象的状态以及结果的方法有三种,分别是`resolve`函数,`reject`函数,以及`throw`关键字,现在我们已经实现了前面两个方法,那么接下来我们来实现第三个`throw`异常的方式来修改`Promise`对象的状态和结果。
```js
let p = new Promise(
(resolve, reject) => {
throw "error";
}
);
console.log(p);
```
我先来看一下当前状态下如果我们`throw`一个异常出来会是什么情况:
![image-20220315151840682](https://file.lynchow.com/2022-03-15-071842.png)
现在是直接报错的,连后面的`console.log`都没有执行,首先这肯定是不对的,我们不能让`throw`在这里中断程序,那么如何才能让一段程序执行过程中报了错但是不会中止程序的运行呢?那么我们下意识是不是就会想到`try/catch`?那么这个`try/catch`应该写在哪呢?
我们来思考一个问题,这个异常是在哪里抛出的?是不是在执行器函数里面?那么执行器函数是在哪里被执行的呢?是不是在`Promise`构造函数中?那么我们这么改一下代码:
```js
function Promise(excutor) {
this.PromiseState = "pending";
this.PromiseResult = null;
const resolve = data => {...};
const reject = data => {...};
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
```
我们拉看一下这段代码,我们在`try`部分来执行`excutor`函数,当`excutor`函数抛出异常之后,就会被`catch`捕获,而我们`throw`出来的异常字符串就会直接被`catch`的形参`e`接收到,那么我们怎么样来修改`Promise`对象的状态和结果呢?是不是直接调用一下`reject`函数就可以实现了?我们来看一下:
![image-20220315152706505](https://file.lynchow.com/2022-03-15-072708.png)
控制台输出的正是一个失败的`Promise`对象,而且结果值就是我们抛出的异常字符串。
以上我们便实现了所有修改`Promise`对象的状态和结果的三种方法。
## 实现`Promise`状态只能修改一次
我们在之前的学习中提到过,任何一个`Promise`对象的状态只能修改一次,只能单方向地从`pending`变为`resolved`或者`rejected`,而且不允许`resolved`和`reject`之间相互转换,但是我们来看一下我们现在的情况下是什么情况:
```js
let p = new Promise(
(resolve, reject) => {
resolve("OK");
reject("error");
}
);
console.log(p);
```
我们在这个执行器函数中既调用了`resolve`函数又调用了`reject`函数,按照`Promise`的设计哲学,我们这里输出的应该是一个成功的`Promise`对象才对,那么我们来看一下结果是什么样子:
![image-20220315153834338](https://file.lynchow.com/2022-03-15-073835.png)
我们发现控制台中输出了一个失败的`Promise`对象,这是不符合我们的预期的,我们来回想一下`Promise`的设计哲学,只允许修改`pending`状态的`Promise`对象的状态,且不可逆,不可逆我们已经做到了,我们没有实现将其他状态改为`pending`状态的方法。那么我们就要做一件事,禁止修改状态不是`pending`的`Promise`对象的状态,那么简单,只要加一个判断就行了:
```js
function Promise(excutor) {
this.PromiseState = "pending";
this.PromiseResult = null;
const resolve = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "fulfilled";
this.PromiseResult = data;
};
const reject = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "rejected";
this.PromiseResult = data;
};
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
```
我们来看一下代码,现在我们在`resolve`函数和`reject`函数中都加了一行判断,在修改状态之前做一下判断,只要状态不是`pending`那么我们直接中止函数执行。那么我们现在再来看一下刚才代码的结果:
![image-20220315154509670](https://file.lynchow.com/2022-03-15-074511.png)
现在正如我们预期一样输出的是一个成功的`Promise`对象。到这一步我们便实现了`Promise`的状态只能修改一次的规则。
## `then`方法调用回调函数
我们目前已经实现了很多属性和规则了,但是现在我们还有一个核心的功能并没有实现,就是`then`方法,我们虽然实现了`then`方法的基本结构,已经可以指定回调了,但是我们的回调还并没有被执行。那么接下来我们就来实现以下。
首先我们要弄清楚我们的回调是在哪里被执行的,首先如果`Promise`执行器函数中如果执行的是同步任务,那么是先修改状态,然后再指定回调,紧接着马上执行回调,那么我们先来实现同步任务的回调执行,这个时候回调是在哪里执行的呢?是不是在`then`方法中执行的?那么我们来修改一下代码:
```js
Promise.prototype.then = (onResolved, onRejected) => {
if (this.PromiseState === "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState === "rejected") {
onRejected(this.PromiseResult);
}
};
```
我们来看一下这段代码,我们用两个形参来接收回调函数,分别是成功时的回调和失败时的回调,但是我们能直接就在函数体中调用这两个函数吗?是不是不能?因为我们要根据状态来调用对应的回调。所以说我们根据状态来来做一个判断。那我们来看一下效果:
![image-20220315160839447](https://file.lynchow.com/2022-03-15-080841.png)
控制台中好像还是什么都没有输出,这是为什么呢?我们这里`then`方法是不是一个箭头函数?箭头函数又自己的`this`吗?是不是没有?如果箭头函数中要用`this`关键字,会去箭头函数的上一层作用于来找`this`,这个箭头函数的外层中的`this`是什么呢?这里的`this`其实是`window`,所以说我们这里根本没有取到`this.PromiseState`和`this.PromiseResult`,那么我们修改一下代码:
```js
Promise.prototype.then = function (onResolved, onRejected) {
if (this.PromiseState === "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState === "rejected") {
onRejected(this.PromiseResult);
}
};
```
这次我们不用箭头函数,而是直接用匿名函数再来看一下结果:
![image-20220315161252743](https://file.lynchow.com/2022-03-15-081254.png)
这一次我们成功输出了我们的回调执行结果,那么我们让我们的`Promise`对象是一个失败的`Promise`再看一下结果:
![image-20220315161512960](https://file.lynchow.com/2022-03-15-081515.png)
也成功调用了我们指定的回调,那么这次我们再来抛出异常试一下:
![image-20220315161633219](https://file.lynchow.com/2022-03-15-081634.png)
这次我们也可以成功输出我们的回调执行结果。以上便是同步任务时`then`方法执行回调的实现。
但是有一个问题,为什么我们用匿名函数可以呢?因为匿名函数是有自己的`this`的,而我们`then`方法是不是通过实例对象来调用的?所以说用匿名函数的`this`就是实例对象自身,而`PromiseState`和`PromiseResult`属性就在实例对象上,那么就可以通过`this`关键字取到。
## 异步任务回调执行
既然讲完了同步任务的回调执行,那么接下来肯定就是大家都好奇的异步任务回调的执行了,因为`Promise`是解决异步任务的一个新方案,那么肯定用`Promise`封装异步任务的使用场景更多。我们先来看当前状态:
```js
let p = new Promise(
(resolve, reject) => {
setTimeout(
() => resolve("OK"),
1000
)
}
);
p.then(
value => console.log(value),
reason => console.warn(reason)
);
```
我们还是用一个定时器来模拟一个异步任务,让`Promise`对象的状态延迟 1 秒再改变,那么我们来看一下结果:
![image-20220315162645304](https://file.lynchow.com/2022-03-15-082647.png)
控制台什么都没有输出,这是为什么呢?按照同步任务的情况来说,应该会输出`OK`才对啊。我们来分析一下,当我们实例化完`Promise`对象的时候,状态改变了没有?是不是还没有,因为这是一个异步任务,要等 1 秒钟才会修改状态,但是`p.then`方法会等异步任务执行完再调用吗?肯定不会的啊,那么这个时候就会直接指定回调函数,并且调用,可是这个时候`Promise`的状态还没有发生改变,依然还是`pending`,而我们在`then`方法中只对`fulfilled`和`rejected`做了判断,所以导致`then`方法并没有执行任何一个回调。
那么我们来思考一个问题,从`Promise`的设计哲学来说,我们不论`Promise`中开的是同步任务还是异步任务,我们执行回调的前提条件是不是`Promise`的状态已经发生改变了?那么异步任务会在什么时候修改状态呢?是不是当异步任务执行完成并执行`resolve`或者`reject`函数的时候才会修改状态?
我们在讲同步任务的时候提到过,首先我们要弄清楚我们的回调是在哪里被执行的,异步任务完成后调用`resolve`或者`reject`函数修改状态然后调用回调,但是这个时候`then`方法已经执行完了,所以说有没有一种可能异步任务的回调是在`resolve`或者`reject`函数中被执行的呢?比如这样:
```js
function Promise(excutor) {
this.PromiseState = "pending";
this.PromiseResult = null;
const resolve = data => {
if (this.PromiseState !== "pending") return;
this.PromiseState = "fulfilled";
this.PromiseResult = data;
onResolved(this.PromiseResult);
};
...
}
```
但是,有一个问题,这个`onResolved`和`onRejected`都是传给`then`方法的,我们的这个`Promise`函数中的`resolve`和`reject`函数是不是根本就拿不到啊?那么我们是不是应该在`then`方法上做一个手脚,当我们异步任务还没有完成,`Promise`状态还没有改变的时候,`then`方法已经被调用了,但是`then`方法中并没有做对`pending`状态的判断,那么我们就把这个判断加上,但是这个判断里面执行什么呢?我们先来看代码看大家能不能明白:
```js
function Promise(excutor) {
...
this.callback = {};
const resolve = data => {
...
if (this.callback.onResolved) {
this.callback.onResolved(this.PromiseResult);
}
};
const reject = data => {
...
if (this.callback.onRejected) {
this.callback.onRejected(this.PromiseResult);
}
};
...
}
Promise.prototype.then = function (onResolved, onRejected) {
...
if (this.PromiseState === "pending") {
this.callback = { onResolved, onRejected };
}
};
```
我们看见我们在`Promise`函数中添加了一个属性叫`callback`,这个属性是一个对象,然后我们在`then`方法中判断如果是`pending`状态的话就把我们传给`then`方法的两个回调函数都保存到`Promise`对象的`callback`属性里,这样的我们在`resolve`以及`reject`函数中来判断`callback`属性中的`onResolved`与`onRejected`是否为空,如果不为空就调用对应的回调函数,我们现在来看一下结果:
![image-20220315170019917](https://file.lynchow.com/2022-03-15-090021.png)
![image-20220315170047517](https://file.lynchow.com/2022-03-15-090049.png)
现在我们异步任务的`then`以及回调函数的执行也完成了。
## 指定多个回调的实现
我们现在实现了同步任务和异步任务回调的执行,那么还有一个问题,我们如果要指定多个回调函数的话该怎么实现呢?首先我们现在肯定是不行的我们来看一下:
```js
let p = new Promise(
(resolve, reject) => {
setTimeout(
() => resolve("OK"),
1000
)
}
);
p.then(
value => console.log(value),
reason => console.warn(reason)
);
p.then(
value => console.log(value, value),
reason => console.warn(reason, reason)
);
```
我们现在是这个异步任务,然后我们每个状态都指定了两个回调,那么我们看一下结果是什么:
![image-20220315171411336](https://file.lynchow.com/2022-03-15-091412.png)
首先回调执行了,但是好像只执行了第二次指定的回调,这是为什么呢?因为我们每次调用`then`方法,如果状态为`pending`,就都把回调都放在一个对象中并且存储到`Promise`对象的`callback`属性中,当我们第二个回调指定的时候,`Promise`的状态还没有改变,所以第二次存储的时候覆盖了第一次存储的属性。那么这么看来我们`then`方法方法就不太合理了。那么我们这么修改一下:
```js
function Promise(excutor) {
...
this.callbacks = [];
const resolve = data => {
...
this.callbacks.forEach(item => item.onResolved(this.PromiseResult));
};
const reject = data => {
...
this.callbacks.forEach(item => item.onRejected(this.PromiseResult));
};
...
}
Promise.prototype.then = function (onResolved, onRejected) {
...
if (this.PromiseState === "pending") {
this.callbacks.push({ onResolved, onRejected });
}
};
```
我们来看一下代码我们把`callback`属性改成了`callbacks`,并且改成了数组,那么我们在`then`方法中就不用赋值了,我们直接给`push`到数组中就好了,那么我们在`resolve`和`reject`函数中就不能像原来那样调用回调了,我们要遍历数组来执行每一个回调。那么我们来看一下结果:
![image-20220315172330611](https://file.lynchow.com/2022-03-15-092332.png)
![image-20220315172402158](https://file.lynchow.com/2022-03-15-092403.png)
以上便实现了给异步任务指定多个回调。
可能有人要问了,那么同步任务不用再处理`then`方法吗?我们来分析一下,同步任务是不是先改变状态然后再指定回调啊?那么根本不需要把回调存到对象属性中了啊。指定完直接就执行掉了。
本节课的内容暂时就先到这,其他的封装方面的内容我们在下节课再继续介绍。

577
note/lesson07_encapsulation_of_promise2.md

@ -0,0 +1,577 @@
# `Promise`自定义封装(二)
上节课我们已经实现了`Promise`的基本结构,两大属性,并且实现了对`Promise`的状态和结果的修改,而且也实现`then`方法指定回调函数,并对同步异步任务的回调执行分别做了相应的介绍,最后对`Promise`指定多个回调也做了相应的实现与分析。
上节课虽然内容很多,但是依然还有一部分因为篇幅的问题,我们还没有介绍到,那么接下来,我们将对另外一部分的功能实现来做进一步的介绍。
## 同步任务`then`方法返回结果
我们之前的内容中就已经介绍过,`Promise`对象调用`then`方法返回的结果将会是一个新的`Promise`对象。那么我们目前的情况的话有没有实现这一功能呢?我们来测试一下:
```js
let p = new Promise(
(resolve, reject) => resolve("OK")
);
let result = p.then(
value => console.log(value),
reason => console.warn(reason)
);
console.log(result);
```
我们来看这段代码,首先我们实例化一个`Promise`对象,在这个对象中我们封装的是一个同步任务,然后调用`then`方法来指定回调,并且声明一个`result`变量来接收`then`方法的返回值,并且把返回值输出到控制台,那么我们来看一下结果:
![image-20220315203846053](https://file.lynchow.com/2022-03-15-123847.png)
控制台中输出的是一个`undefined`,为什么呢?因为我们定义了`then`方法,但是我们的`then`方法并没有返回值,所以说默认返回的就是一个`undefined`。
那么我们先来回顾一下`then`方法返回一个新的`Promise`对象的设计哲学是什么样子的:
- `then`方法返回的`Promise`对象是一个新的`Promise`对象
- `then`方法返回的`Promise`对象的状态和结果都取决于回调函数执行的结果
- 回调函数执行的结果分为三种情况
- 返回非`Promise`类型数据,`then`方法将返回一个成功的`Promise`对象,结果值为回调函数的返回值
- 返回`Promise`对象,`then`方法将返回一个状态和结果值都与回调函数返回值一直的`Promise`对象
- 抛出异常,`then`方法将返回一个失败的`Promise`对象,结果值为异常字符串
那么我们来修改一下代码吧,首先返回一个新的`Promise`对象:
```js
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
if (this.PromiseState === "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState === "rejected") {
onRejected(this.PromiseResult);
}
if (this.PromiseState === "pending") {
this.callbacks.push({ onResolved, onRejected });
}
});
};
```
我们用最简单的方法就直接在`return`的时候`new`一个新的`Promise`对象。但是我们之前`then`方法中的代码还是要执行的啊,那么就应执行器函数来包裹住之前的代码,可能有人会问,我们能不能把之前代码放在`return`之前呢?就目前来说是没有问题的,因为之前`then`方法中的内部代码也都是同步代码,那么在`return`之前或者是用执行器函数包裹住都没有区别,因为执行器函数也是立即同步被执行的。我们来看一下结果是什么样:
![image-20220315205353716](https://file.lynchow.com/2022-03-15-125355.png)
我们看到控制台中输出的不再是`undefined`了,而是一个`Promise`对象,那么现在我们实现了第一条,`then`方法返回的是一个新的`Promsie`对象。但是有一点,我们这里输出的`Promise`对象的状态和结果值好像都有点问题。
我们来看我的第二条,`then`方法返回的`Promise`对象的状态和结果值都取决于回调函数的执行结果。那么我们来看一下我们的回调函数是什么,我们执行的是`value => console.log(value)`,首先这个函数没有返回值,其次,控制台上也成功输出了`value`,说明回调函数没有抛出异常。
那么好,我们来看一下第三条,有三种情况,那么这属于那种情况呢?没有返回值就是`undefined`,而且也没有抛出异常,是不是刚好就是第一种情况,返回了非`Promise`类型的数据?那么我们就应该返回一个成功的`Promise`对象,而且结果值为回调函数的返回值,也就是`undefined`,那么我们再来修改一下代码:
```js
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
if (this.PromiseState === "fulfilled") {
let result = onResolved(this.PromiseResult);
resolve(result);
}
if (this.PromiseState === "rejected") {
let result = onRejected(this.PromiseResult);
reject(result);
}
...
});
};
```
我们来看这段代码是什么意思呢?根据`Promise`设计哲学的第二条,`then`方法返回的`Promise`对象的状态和结果值由回调函数的执行结果来决定。
我们在第一步实例化的`Promise`对象`p`的状态是个什么状态?是不是一个成功的`Promise`?那么在调用`then`方法的时候是不是一定会根据状态去调用`onResolved`函数?这个`onResolved`函数是哪来的?是不是就是我们调用`then`方法时候传进去的,那么这里的话是不是就是`value => console.log(vale)`?那么这个函数的返回值是不是就应该是`then`方法返回的`Promise`对象的结果值?那么我们声明一个变量`result`来接收这个返回值。
那么接下来问题来了,我们结果值有了,但是`then`方法返回的`Promise`对象的状态我们该怎么修改呢?首先我们的`then`方法返回的是什么?是不是我们这里`new`出来的一个新的`Promise`?那么一个`Promsie`如果想改变状态要怎么做?这很简单啊,直接在执行器函数中去调用`resolve`或者`reject`函数就可以了,因为第一种情况永远返回的都是一个成功的`Promise`啊,这样我们调用`resolve`函数,并把`result`传进去一次性把状态和结果值都解决了。
所以说通过这一部分的分析,我们再回头来思考刚才的问题,我们`then`方法中的状态判断的这部分代码可以放在`return`之前吗?好像也可以,但是会比直接放在执行器函数中要麻烦许多。
那么我们现在再看一下结果:
![image-20220315211916894](https://file.lynchow.com/2022-03-15-131919.png)
![image-20220315212003423](https://file.lynchow.com/2022-03-15-132004.png)
我们看见了现在我们的输出好像还是有点问题,当我们第一步实例化的`Promise`的状态是`rejected`的话,我们是应该调用`onRejected`函数的,我们这里指定的`onRejecte`函数是什么?`reason => console.warn(reason)`,这个回调函数也没有返回值啊,那么也是`undefined`,所以`then`方法返回的应该也是一个成功的`Promise`,但是我们输出的却是一个失败的`Promise`。
不知道大家在看完上面那段代码的时候有没有发现一个错误,我这里先不说是什么错误,我先把代码修改一下:
```js
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
if (this.PromiseState === "fulfilled") {
let result = onResolved(this.PromiseResult);
if (result instanceof Promise) {
...
} else {
resolve(result);
}
}
if (this.PromiseState === "rejected") {
let result = onRejected(this.PromiseResult);
if (result instanceof Promise) {
...
} else {
resolve(result);
}
}
...
});
};
```
大家来看这段代码,有没有发现和之前代码有什么不一样的地方?我们的回调函数的返回值是不是有两种情况?一种是`Promise`对象,另一种不是`Promise`对象,当不是`Promise`对象时,`then`方法将永远返回一个成功的`Promsie`,之前的代码中我们忘了做判断。
现在我们对回调函数的返回值的类型来做一个判断,当回调函数返回值不是一个`Promise`对象时,不论第一步实例化的`Promise`对象是否成功都调用`resolve`函数来改变新的`Promise`的状态和结果值。
![image-20220315213947060](https://file.lynchow.com/2022-03-15-133948.png)
![image-20220315214018505](https://file.lynchow.com/2022-03-15-134019.png)
那么如果回调函数返回的是一个`Promise`对象的话,又该怎么处理呢?我们知道如果回调函数返回的是一个`Promsie`对象的话那么这个`Promsie`对象的状态和结果值就是`then`方法返回的`Promise`对象的状态和结果值。那么我们应该怎么处理呢:
```js
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
if (this.PromiseState === "fulfilled") {
let result = onResolved(this.PromiseResult);
if (result instanceof Promise) {
result.then(
value => resolve(value),
reason => reject(reason)
);
} ...
}
if (this.PromiseState === "rejected") {
let result = onRejected(this.PromiseResult);
if (result instanceof Promise) {
result.then(
value => resolve(value),
reason => reject(reason)
);
} ...
}
if (this.PromiseState === "pending") {
this.callbacks.push({ onResolved, onRejected });
}
});
};
```
我们来看代码,这是什么操作呢?我们来想一下,如果回调函数返回的是一个`Promise`对象的话,那么这个`Peomise`对象是不是肯定可以调用`then`方法?那么是不是就代表着我们可以通过`then`方法来指定让这个`Promise`对象分别在成功和失败的时候来调用`resolve`函数和`reject`函数来修改`then`方法返回的`Promsie`对象的状态和结果值?
```js
let result = p.then(
value => {
console.log(value);
return new Promise((resolve, reject) => {
resolve("OK")
});
},
reason => {
console.warn(reason);
return new Promise((resolve, reject) => {
reject("error");
});
}
);
console.log(result);
```
那么我们这次来让成功和失败的回调分别返回一个成功和失败的`Promsie`对象,我们再来看一下结果:
![image-20220315215207192](https://file.lynchow.com/2022-03-15-135209.png)
第一步实例化的`Promise`对象是个成功的`Promise`,然后执行成功时的回调,回调函数返回一个成功的`Promise`对象,结果值是`OK`,我们看见控制台中输出的`Promise`对象的状态和结果值与回调函数返回的`Promise`对象一致。
![image-20220315215521176](https://file.lynchow.com/2022-03-15-135522.png)
现在我们第一步实例化的`Promise`对象是一个失败的`Promsie`,然后执行失败时的回调,回调函数返回一个失败的`Promise`对象,结果值是`error`,我们看见控制台中输出的`Promsie`对象的状态和结果值与回调函数返回的`Promise`对象一直。
这样我们便实现了`Promise`设计哲学中`then`方法返回值规则的第一条,第二条以及第三条的前两种情况。
那么第三种情况呢?我再回调函数中抛出异常的话应该怎么处理呢?
```js
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
if (this.PromiseState === "fulfilled") {
try {
let result = onResolved(this.PromiseResult);
...
} catch (e) {
reject(e);
}
}
if (this.PromiseState === "rejected") {
try {
let result = onRejected(this.PromiseResult);
...
} catch (e) {
reject(e)
}
}
...
});
};
```
为了节省篇幅我就把部分代码省略了,将之前的代码全部放在`try`代码块中,然后用`catch`来捕获回调函数中的异常,当回调函数中抛出异常时,将直接调用`reject`函数来修改状态。
![image-20220315221214562](https://file.lynchow.com/2022-03-15-141216.png)
![image-20220315221255879](https://file.lynchow.com/2022-03-15-141257.png)
以上便是对`Promise`同步任务调用`then`方法返回一个新的`Promsie`对象的实现。
## 异步任务`then`方法返回结果
我们前面说了同步任务的`then`方法返回结果。但是`Promise`异步任务的`then`方法返回情况,根据我们之前的经验来看肯定适合同步任务是不一样的,我们来回想一下我们之前让`then`方法调用回调函数的时候是什么情况?
- 同步任务直接在`then`方法函数体中判断`resolved`和`rejected`状态,并直接执行回调
- 异步任务在修改状态之前`then`方法已经完成调用,但是`resolved`和`rejected`状态都匹配不上,所以要在`then`方法中判断`pending`状态并保存传入的回调函数,然后在`Promsie`状态发生改变之后才执行回调函数
那么现在`then`的返回值情况我们来看一下是什么样子:
```js
let p = new Promise(
(resolve, reject) => setTimeout(
() => resolve("OK"),
1000
)
);
let result = p.then(
value => {
console.log(value);
},
reason => {...}
);
console.log(result);
```
我们来看代码,我们实例化了一个`Promise`对象,并开启一个异步任务,让`Promise`对象延迟一秒将状态修改为成功,然后我们来看一下`then`方法返回的结果:
![image-20220316095140755](https://file.lynchow.com/2022-03-16-015142.png)
我们发现返回的结果并不符合我们的预期,为什么呢?是不是和之前执行回调函数一样?因为执行`then`方法的时候我们还没有执行回调函数,那么就会去判断`pending`状态,但是`pending`状态中我们只是存储回调函数到我们第一步实例化的`Promise`对象上,而回调在执行的时候修改的也是我们第一步实例化的`Promise`对象的状态,所以说我们`then`方法返回的就是一个没有改变过状态还结果值的`Promise`对象。
那么我接下来的思路是不是就要在`pending`状态这个分支来做手脚了?那么我们怎么处理呢?
```js
Promise.prototype.then = function (onResolved, onRejected) {
return new Promise((resolve, reject) => {
...
if (this.PromiseState === "pending") {
this.callbacks.push({
onResolved: () => {
let result = onResolved(this.PromiseResult);
if (result instanceof Promise) {
} else {
resolve(this.PromiseResult);
}
},
onRejected: () => {...}
});
}
});
};
```
我们来看代码,我们这里为了节省篇幅就把之前的代码省略了,我们来看对`pending`状态的判断分支,首先最外层我们依然还是哪个操作,我们要把回调封装成对象存储到第一步实例化的`Promise`对象的`callbacks`属性上。
但是我们对回调函数的封装可就不能像原来一样了,我们定义一个函数,在这个函数中执行回调,那么这个回调被执行的结果就可以决定`then`方法返回的`Promise`对象的状态和结果。
那么我们接下来就要通过这个回调的执行结果来修改`then`方法要返回的`Promise`对象的状态。那么我们是不是也要判断回调返回值的类型?如果返回的不是`Promise`对象,那就把`then`返回的`Promise`对象状态改为成功。我们来看一下结果:
![image-20220316102121307](https://file.lynchow.com/2022-03-16-022123.png)
![image-20220316102235185](https://file.lynchow.com/2022-03-16-022236.png)我们看见现在我们回调现在返回的都不是`Promise`对象,输出结果也都是没有问题的,那么如果回调函数返回的是一个`Promise`对象的话呢?那和同步任务是不是也类似?既然回调返回的是`Promise`对象,那么就可以调用`then`方法,那么我们根据`then`方法来进行相关的处理就可以了,包括回调函数抛出异常也是和同步任务的处理一样,大家尝试自己来补全代码,都是同一个原理,这里就不再赘述了
以上我们便实现了异步任务的`then`方法返回一个新的`Promise`对象。
可能大家在写完之后会发现我们好像写了好多重复代码啊,我希望大家能有自己优化代码的能力,我最后将会我的完整代码放在[我的`git`仓里](https://qithub.lynchow.com/jingxun/promise.git),大家有需要的可以来参考,我这里就不贴代码了。
## `catch`方法以及异常穿透
我们现在基本算是已经实现了`then`方法了,但是我们还有一个很重要的功能,就是异常穿透。因为我们`then`方法是要进行链式调用的啊,如果调用链中间某一环出现了异常我们没有异常穿透的话在每一环都要`catch`一下才行,那么我们异常穿透要怎么实现呢?首先肯定要实现`catch`功能,那么我们现在如果在`then`方法后面调用`catch`的话会怎么样呢?
```js
let p = new Promise((resolve, reject) => {
setTimeout(() => reject("error"),1000);
});
p.then(
value => console.log(111)
).then(
value => console.log(222)
).catch(
reason => console.warn(reason)
);
```
我们先这样实例化一个`Promise`对象,并且让`Promise`对象状态为失败,而我们链式调用`then`方法,而且只指定成功时的回调,我们来看一下结果是什么样子,按照预期的话应该会被`catch`方法捕获:
![image-20220316105858153](https://file.lynchow.com/2022-03-16-025859.png)
报错了,告诉我们`catch`不是一个函数,为什么呢?因为我们根本都没有给`Promise`来添加一个`catch`方法啊,所以这里肯定是会报错的,那么我们就把这个`catch`方法给加上:
```js
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
}
```
我们之前在介绍`catch`方法的时候就说过,`catch`是把`then`方法做了一个独立封装,而且`catch`只接收一个函数类型的参数,并且会返回一个失败状态的`Promise`对象,那么我们就直接拿`then`方法来封装就好了,我们来看一下结果:
![image-20220316111417123](https://file.lynchow.com/2022-03-16-031419.png)
现在不报错了,但是我们的输出结果好像不太对劲。我们应该输出`error`才对啊,这是为什么呢?我们来分析一下,我们`Promise`对象里面开的是异步任务对不对?那么调用`then`的时候是不是状态还没改变?然后就把我们指定的回调都保存到实例对象上了?但是我们是不是根本没有指定失败时的回调啊?那么当我们这个`Promise`的异步任务执行完成之后,把`Promise`的状态修改成了失败,那么肯定要去调用失败时的回调,但是我们没有指定失败时的回调,使用就输出了这么个问题。
但是`Promise`是允许我们在链式调用中只指定一个回调另一个回调不传的,但是不传不代表`Promise`内部底层没有,不然的话我们传`undefined`进去也肯定会报错的,那么我们是不是也应该在`then`方法中来做一个相应的处理来解决这个问题呢?
```js
Promise.prototype.then = function (onResolved, onRejected) {
if (typeof onRejected !== "function") {
onRejected = reason => { throw reason; };
}
if (typeof onResolved !== "function") {
onResolved = value => value;
}
return new Promise((resolve, reject) => {...});
};
```
我们来看这段代码,我们判断了一下`onRejected`和`onResolved`这两个形参的类型,如果是函数那就继续,如果不是函数,那么我们就来指定一个默认函数,`onRejected`指定的默认函数大家应该都能明白,如果`onRejected`传入进来不是函数,那么通常可能代表着我们没有传这个参数,那么我们如果异步任务是失败的,但是我们却没有传这个回调,那么我们就抛出一个异常让`catch`去捕获。
但是`onResolved`指定的默认函数是什么意思呢?这个是`Promise`的一个值传递,比如我这个`Promise`对象是成功的,但是我在调用`then`的时候并没有指定成功时的回调函数,那么就将通过这个默认函数来传递`value`值供`then`方法来返回一个新的`Promise`对象来进行接下来的链式调用。
那么我们现在来看一下失败时会怎么样:
![image-20220316113007444](https://file.lynchow.com/2022-03-16-033009.png)
现在`catch`方法成功被调用并输出了我们预期的结果。那么我们来测一下成功时的值传递:
```js
let p = new Promise((resolve, reject) => {
setTimeout(() => resolve("OK"),1000);
});
p.then(
).then(
value => console.log(222)
).catch(
reason => console.warn(reason)
);
```
这次我们在`then`方法调用链的第一环并没有指定任何回调,来看一下结果:
![image-20220316113226465](https://file.lynchow.com/2022-03-16-033228.png)
成进行了后面的链式调用并输出了相应结果。以上便是我们对`catch`方法以及异常穿透的实现。
## `resolve`和`reject`函数的封装
看到这个标题大家可能会很奇怪,`resolve`和`reject`函数不是早就封装好了吗?这里的`resolve`和`reject`函数和我们之前说的不是一回事,之前的是`Promise`函数里的内置函数,这里是指`Promise`的`API`中的`Promise.resolve`和`Promise.reject`。那么我们首先先看一下我们目前直接调这两个方法会是什么结果:
![image-20220316130629408](https://file.lynchow.com/2022-03-16-050631.png)
![image-20220316130702717](https://file.lynchow.com/2022-03-16-050706.png)
报错了说`resolve`和`reject`不是一个函数,为什么呢?因为我们没有给`Promise`添加`resolve`和`reject`方法,那么我们现在来把这两个方法添加上:
```js
Promise.resolve = function(value){
}
Promise.reject = function(reason){
}
```
我们要注意一点,我们在给`Promise`添加`resolve`和`reject`方法时要明确一点就是这两个方法是`Promise`函数对象的,而不是`Promise`实例对象的,所以我们不能用`Promise.prototype`,好了现在我们有了这两个方法了,我们来回忆一下这两个方法是用来干什么的。
- `Promise.resolve`将返回一个成功或者失败的新的`Promise`对象
- 如果传入的参数不是`Promise`对象,则返回一个成功的`Promise`对象,且结果值是我们传入的`value`
- 如果传入的参数是一个`Promise`对象,则返回一个状态和结果值都与传入的`Promise`对象相同的新的`Promise`对象
- `Promise.reject`将返回一个失败的新的`Promise`对象,且返回的`Promise`对象的结果值就是我们传入的值
那么我们应该这么实现我们这两个方法的内部逻辑呢?我们来看一下代码:
```js
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(
v => resolve(v),
r => reject(r)
);
} else {
resolve(value);
}
});
}
Promise.reject = function (reason) {
return new Promise((resolve, reject) => reject(reason));
}
```
首先来看两个函数的最外层的操作,我们都`return`一个新的`Promise`对象,先来看`resolve`,我们传入了`value`,有两种情况,`Promise`对象,或者不是`Promise`对象,那么我们就要判断一下,如果不是`Promise`对象,就直接调`resolve`函数修改状态,如果是`Promise`对象,那就调用`then`方法在回调里面修改状态
然后我们来看`reject`函数,这个就简单了,因为`reject`函数只会返回一个失败的`Promise`而且我们传什么,结果值就是什么,那么直接调用`reject`来改状态就好了。
```js
let p1 = Promise.resolve("OK");
let p2 = Promise.resolve(new Promise((resolve, reject) => reject("OH NO")));
let p3 = Promise.reject("Error");
let p4 = Promise.reject(new Promise((resolve, reject) => reject("F**K")));
console.log(p1);
console.log(p2);
console.log(p3);
console.log(p4);
```
我们现在用这两个方法来生成了 4 个`Promise`对象,然后输出到控制台上我们来看一下结果:
![image-20220316133628837](https://file.lynchow.com/2022-03-16-053630.png)
我们看到我们控制台中输出了相应的结果也是完全符合`Promise`规则的。
以上便是`resolve`和`reject`函数的封装。
## `all`方法的封装
前面我们实现了`then`,`catch`,`resolve`,`reject`以及多个核心功能,其实到这自定义封装距离结束已经不远了,接下来呢我们来介绍一下`all`方法的封装。
首先我们来回忆一下`all`方法是干什么的。这个方法也是`Promise`函数对象自身的一个方法,接收一个`Promise`实例对象的数组,并返回一个新的`Promise`对象,如果数组中所有`Promise`对象都是成功的,那么返回的新的`Promise`就是成功的,而且结果值是数组中所有`Promise`对象的结果值组成的数组。如果数组中有失败的`Promise`对象,那么返回的`Promise`对象的状态就是失败的,而结果值则是失败的那个`Promise`对象的结果值。
那么我们就不做测试了,我们现在还没有给`Promise`函数添加`all`方法,直接测试肯定报错,我们先来添加方法吧。
```js
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
});
}
```
我们来看一下,这样我们就实现了`all`方法的一个基本结构,但是这样的话肯定不符合我们的需求的,首先我们接收的形参是一个数组,而且只有当数组中的`Promise`对象都是成功的时候才能把我们返回的`Promise`对象状态修改为成功。那么我们怎么判断数组中的`Promise`对象是不是都是成功的?那么是不是要`for`循环?
```js
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => { },
reason => reject(reason)
);
}
});
}
```
我们来看这段代码,我们遍历整个数组,这个数组里面所有的元素都是`Promise`对象,那么只要是`Promise`对象那么就可以调用`then`方法,然后我们来指定回调,成功的回调函数我现在还没有实现,我们先来看失败的回调,`all`方法的逻辑是什么?只有数组中有任何一个失败的`Promise`对象,那么就不用再考虑其他的了,直接返回一个失败的`Promise`对象,而且结果值就是这个失败的`Promise`对象的结果值,那么我们是不是直接调用`reject`函数来修改状态就行了?我们来测试一下:
```js
let p1 = Promise.resolve("OK");
let p2 = Promise.reject("Error");
let p3 = Promise.resolve("Yes");
let result = Promise.all([p1, p2, p3]);
console.log(result);
```
我们创建了三个`Promise`对象,其中有一个是失败的,看一下结果:
![image-20220316141811318](https://file.lynchow.com/2022-03-16-061812.png)
我们看到控制台输出的是一个失败的`Promise`对象,而且结果值就是失败的那个`Promise`对象的结果值,那么如果三个都是成功的应该这么实现呢?
```js
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let count = 0;
let result = [];
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => {
count++;
result[i] = value;
if (count === promises.length) resolve(result);
},
reason => reject(reason)
);
}
});
}
```
我们这次来看这段代码,我们在`for`循环中肯定是不能直接执行`resolve`函数的,因为我们要等到确定所有的`Promise`对象都是成功的才能执行`resolve`函数,所以我们预先声明一个变量,每当成功的回调被执行都让它自增,等到这个变量和数组长度相等的时候那是不是就代表着整个数组里面都是成功的`Promise`,那么我们就可以执行`resolve`函数了。
但是我们现在可以修改状态了,但是结果值呢?我们说了如果全是成功,返回的结果值是这些数组里所有`Promise`对象的结果值组成的数组。那么我们就要预先声明一个数组用于存放结果值,但是我们这里为什么没有用`push`方法向数组里面添加结果呢?
因为结果值数组的顺序要和`promises`数组顺序一致,这样的话就可以保证不会因为异步任务完成的时间不同而导致顺序错乱,那么我们 这次用三个成功的`Promise`对象来测一下:
![image-20220316142956284](https://file.lynchow.com/2022-03-16-062957.png)
我们看见这次输出的`Promise`对象是一个成功的,而且结果值也是按照之前顺序的一个数组。以上我们便完成了对`all`方法的封装。
## `race`方法的封装
接下来我们来实现最后一个`API`,`race`方法和`all`方法很像,但是`race`方法不用像`all`方法那样来确定数组中每个`Promise`对象的状态,这是一个数组中的`Promise`对象赛跑的机制,谁最先改变状态,谁的状态和结果值就是`race`方法返回的`Promise`的状态和结果值,那么就简单了,我们先来添加方法:
```js
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
value => resolve(value),
reason => reject(reason)
);
}
});
}
```
我们来看一下代码,可能有人要问,这次我们为什么不计数了呢?因为用不着了,我们这里的返回值状态取决于第一个改变状态的`Promise`对象,所以说直接改就行了那么我们来测一下:
```js
let p1 = new Promise((resolve, reject) => setTimeout(() => reject("Error"), 1000));
let p2 = Promise.resolve("It's OK");
let p3 = Promise.reject("OH NO");
let result = Promise.all([p1, p2, p3]);
console.log(result);
```
我们这次让第一个`Promise`对象封装一个异步任务,那么我们来分析一下哪个`Promise`对象最先改变状态,首先肯定不是`p1`对吧,因为`p1`开了异步任务,要等 1 秒才能修改状态,那么来到`p2`,这是个同步的,直接就修改了状态,然后才执行`p3`所以说结果输出的应该是一个成功的`Promise`对象,结果值和`p2`的结果值一致:
![image-20220316144742680](https://file.lynchow.com/2022-03-16-064744.png)
如此一来我们便实现了`Promise`中所有的`API`。
## `then`回调函数异步执行
我接下来来实现一个细节上的东西。我们先来看`Promise`的一个细节:
```js
let p = new Promise((resolve,reject)=>{
resolve("OK");
console.log(111);
});
p.then(v=>console.log(222));
console.log(333);
```
我们来看一下`Promise`的结果是什么样子:
![image-20220316145655643](https://file.lynchow.com/2022-03-16-065657.png)
我们看控制台上输出的内容好像和我想的不一样啊,因为`Promise`在设计的时候是吧`then`中的回调异步执行的,那我们来看一下我们自己手写的`Promise`:
![image-20220316145829828](https://file.lynchow.com/2022-03-16-065831.png)
我们自己写的好像和`Promise`的设计不符合啊,那么我们用`setTimeout`函数来把我们回调调用部分包裹起来是不是就实现异步了?代码我这里就不贴了,大家自己尝试实现一下。
理论上我们接下来要介绍一下怎么把我们的`Promise`封装成一个类,因为到现在为止我们都是函数式编程,但是我觉得大家可以自己尝试把函数式编程封装成类,我们这里就不赘述了。
Loading…
Cancel
Save