手写Promise教程_then方法
手写Promise教程_then方法
1、Promise结构分析
console.log(new Promise((resolve, reject) => {}));
通过打印Promise的实例对象,观察promise的对象结构:
分析上图,可以得出一些信息:
- 1、promise实例对象上有PromiseState属性和PromiseResult属性
- 2、Promise构造函数上有all方法、race方法、resolve方法、reject方法。
- 3、Promise构造函数的原型对象上有then方法、catch方法。
这些是Promise最核心的属性和方法,手写Promise也就是去实现它们的功能。
2、构造函数
创建一个promise_test.js文件,被在html文件中中引入:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手写promise教程</title>
<script src="./promise_test.js"></script>
</head>
<body>
<script>
new Promise((resolve, reject) => {});
</script>
</body>
</html>
promise_test.js:
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = undefined;
executor(resolve, reject);
}
在promise_test.js文件中写上一个Promise构造函数,把JS为我们提供的原生Promise构造函数覆盖掉,然后运行代码,报错:
Uncaught ReferenceError: resolve is not defined。
为什么会出现这个错误呢?
因为在index.html中new Promise( 匿名函数(resolve, reject) ),作为实参传进来的匿名函数(resolve, reject),在构造函数内调用的时候,传入了resolve和reject实参方法,但构造函数中没有这两个方法,因此我们要在构造函数中定义resolve和reject这两个方法:
2.1、resolve 和 reject
功能分析:
- 1、修改PromiseState的状态为fulfilled,并且PromiseState的状态只能修改一次。
- 2、可以接收一个参数,将这个参数的值赋给PromiseResult。
index.html:
new Promise((resolve, reject) => { resolve('test'); });
promise_test.js:
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = undefined;
// 定义resolve函数
function resolve(data) {
console.log(this.PromiseState); // undefined
}
executor(resolve, reject);
}
既然要修改PromiseState的值,那么我们先尝试获取这个值,构造函数中声明的是pending,但结果却是:undefined,为什么?
this的指向问题,我们在代码中打印了了一下this,结果:Window,为什么是Window?
这里复习一下this的指向,1、普通函数中的this指向window。2、构造函数中的this指向实例对象。3、箭头函数中的this指向父级作用域的this。
ok,如果知道了这两点的话,那么就可以知道如何解决:1、将this的指向作为一个属性保存下来。2、使用箭头函数。
这里选择保存this的指向:
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = undefined;
// 保存实例对象的this值
const _this = this;
// 定义resolve函数
function resolve(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'fulfilled'; // resolved
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
}
function reject(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'rejected'; // rejected
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
}
executor(resolve, reject);
}
index.html:
测试1:
const p = new Promise((resolve, reject) => {
resolve('success');
});
console.log(p);
结果1:Promise {PromiseState: 'fulfilled', PromiseResult: 'success'}
——
测试2:
const p = new Promise((resolve, reject) => {
reject('fail');
});
console.log(p);
结果2:Promise {PromiseState: 'rejected', PromiseResult: 'fail'}
——
经过测试,PromiseState和PromiseResult成功被修改!
3、then方法
3.1、功能分析
通过1的分析,我们可以得知then方法是放在原型对象上的,这样做的目的是为了通过实例对象调用.then进行链式调用。
链式调用是Promise最重要的特性,它解决了回调地狱的问题,下面我们来分析一下then方法的功能:
1、Promise.prototype.then方法:then(onResolved, onRejected) => {},
它传入两个回调函数作为形参,onResolved作为PromiseState为fulfilled时的回调函数,onRejected作为PromiseState为rejected时的回调函数。
验证代码:
const p = new Promise((resolve, reject) => {
// resolve将PromiseState状态改为了fulfilled
resolve('success');
// reject将PromiseState状态改为了rejected
// reject('fail');
});
p.then(value => {
// onResolved方法的回调函数,状态时fulfilled执行
console.log(value); // success
}, reason => {
// onRejected方法的回调函数,状态时rejected执行
console.log(reason); // fail
});
2、then方法的返回值是一个新的promise对象,这个新的promise对象的结果状态由then指定的回调函数执行的返回值决定。
- 如果then回调函数的返回值是:非Promise的数据。那么新promise的状态(PromiseState)是fulfilled,值(PromiseResult)是这个非Promise的数据。
- 如果then回调函数的返回值是:Promise类型的数据,那么新的promise的状态(PromiseState)由返回值promise对象的状态决定,值(PromiseResult)也由它的值决定。
- 如果抛出异常,新promise变成rejected,reason为抛出的异常。
验证代码:
const p = new Promise((resolve, reject) => {
// resolve将PromiseState状态改为了fulfilled
resolve('success');
// reject将PromiseState状态改为了rejected
// reject('fail');
});
const p2 = p.then(value => {
// onResolved方法的回调函数,状态时fulfilled执行下面的代码
// 返回值是value变量,非promise,也就是'success'
// return value;
// 返回值是promise对象
return new Promise((resolve, reject) => {
// 如果调用resolve,那么p2的PromiseStatus就是fulfilled,PromiseResult是success2
resolve('success2');
// 如果调用reject,那么p2的PromiseStatus就是rejected,PromiseResult是fail2
// reject('fail2');
// 如果抛出异常,那么p2的PromiseStatus就是rejected,PromiseResult是error
// throw 'error';
});
}, reason => {
// onRejected方法的回调函数,状态时rejected执行下面的代码
console.log(reason); // fail
});
console.log(p2);
3、链式调用,并且异步调用。
- 1、then里面的代码,会按照链式调用的顺序,顺序执行。
- 2、then里面的代码,是异步执行。
验证代码:
const p = new Promise((resolve, reject) => {
resolve();
console.log('111');
});
const p2 = p.then(value => {
// 没有return返回值,默认是return undefined,这个也是非promise的数据,因此状态是fulfilled,值是undefined
console.log('222');
}, reason => {
console.log(reason); // fail
});
p2.then(value => {
console.log('333');
});
console.log('444');
执行结果:111 444 222 333
可以看到,同步代码先执行,执行完之后再执行then中的异步代码,并且是按照链式操作的顺序执行下来。
以上就是then方法的功能分析,下面是手写实现:
3.2、功能实现
1、实现异步执行 + 判断返回值进行不同的操作
Promise.prototype.then = function(onResolved, onRejected) {
// then函数的返回值是一个新的promise对象
return new Promise((resolve, reject) => {
// 如果实例对象上的PromiseState是fulfilled,执行onResolved
if (this.PromiseState === 'fulfilled') {
// 异步执行
setTimeout(() => {
try {
let result = onResolved(this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
});
}
// 如果实例对象上的PromiseState是fulfilled,执行onRejected
if (this.PromiseState === 'rejected') {
// 异步执行
setTimeout(() => {
try {
let result = onRejected(this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
});
}
});
}
上述代码看似可以完美实现,但是却存在一个缺陷,以下是测试index.html:
const p = new Promise((resolve, reject) => {
// 如果这里是异步的,出现bug:永远不会触发then方法
setTimeout(() => {
resolve('111');
}, 1000);
});
const p2 = p.then(value => {
console.log('then中:' + value);
}, reason => {
console.log(reason);
});
上述测试,及时是等一秒甚至一百秒,then方法中的回调也不会执行,为什么?
因为这个时候p这个对象中的promiseState还处于pending状态,所以我们要在then方法中加上第三种情况,pending。
做法是:将回调函数保存起来,在resolve和reject方法中去调用,如此就可以实现链式调用。
2、链式调用的实现
promise_test.js:
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = undefined;
// 用于保存回调函数的数组
this.callbacks = [];
// 保存实例对象的this值
const _this = this;
// 定义resolve函数
function resolve(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'fulfilled'; // fulfilled
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
// 3、调用异步任务下,成功的回调函数
setTimeout(() => {
_this.callbacks.forEach(item => {
item.Resolved(data);
});
});
}
function reject(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'rejected'; // rejected
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
// 3、调用异步任务下,失败的回调函数
setTimeout(() => {
_this.callbacks.forEach(item => {
item.Rejected(data);
});
});
}
executor(resolve, reject);
}
Promise.prototype.then = function(onResolved, onRejected) {
// then函数的返回值是一个新的promise对象
return new Promise((resolve, reject) => {
// 如果实例对象上的PromiseState是fulfilled,执行onResolved
if (this.PromiseState === 'fulfilled') {
// 异步执行
setTimeout(() => {
try {
let result = onResolved(this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
});
}
// 如果实例对象上的PromiseState是rejected,执行onRejected
if (this.PromiseState === 'rejected') {
// 异步执行
setTimeout(() => {
try {
let result = onRejected(this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
});
}
// 如果new Promise中的函数是异步的,例如一秒之后再调用resolveo或者reject的情况
// 后续的新promise对象的状态都是pending,都会走这里。
// 我们就将回调函数保存起来,在resolve和reject方法中去调用
if (this.PromiseState === 'pending') {
// 注意保存一下this的指向,因为下面两个都是普通函数,内部的this指向是window
const _this = this;
function Resolved() {
try {
let result = onResolved(_this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
}
function Rejected() {
try {
let result = onRejected(_this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
}
this.callbacks.push({
Resolved,
Rejected
});
}
});
}
3、链式调用的实现_代码优化
可以看出,上述有很多重复代码,是为了更好帮助理解,故意的。
以下则是优化后的代码promise_test.js:
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = undefined;
// 用于保存回调函数的数组
this.callbacks = [];
// 保存实例对象的this值
const _this = this;
// 定义resolve函数
function resolve(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'fulfilled'; // fulfilled
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
// 3、调用异步任务下,成功的回调函数
setTimeout(() => {
_this.callbacks.forEach(item => {
item.Resolved(data);
});
});
}
function reject(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'rejected'; // rejected
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
// 3、调用异步任务下,失败的回调函数
setTimeout(() => {
_this.callbacks.forEach(item => {
item.Rejected(data);
});
});
}
executor(resolve, reject);
}
Promise.prototype.then = function(onResolved, onRejected) {
// 注意保存一下this的指向,因为下面普通函数,内部的this指向是window
const _this = this;
// then函数的返回值是一个新的promise对象
return new Promise((resolve, reject) => {
function handler(func) {
try {
let result = func(_this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
}
// 如果实例对象上的PromiseState是fulfilled,执行onResolved
if (this.PromiseState === 'fulfilled') {
// 异步执行
setTimeout(() => {
handler(onResolved);
});
}
// 如果实例对象上的PromiseState是rejected,执行onRejected
if (this.PromiseState === 'rejected') {
// 异步执行
setTimeout(() => {
handler(onRejected);
});
}
// 如果new Promise中的函数是异步的,例如一秒之后再调用resolveo或者reject的情况
// 后续的新promise对象的状态都是pending,都会走这里。
// 我们就将回调函数保存起来,在resolve和reject方法中去调用
if (this.PromiseState === 'pending') {
this.callbacks.push({
Resolved: () => {
handler(onResolved);
},
Rejected: () => {
handler(onRejected);
}
});
}
});
}
测试代码index.html:
const p = new Promise((resolve, reject) => {
// 开启异步
// setTimeout(() => {
// resolve('ok');
reject('no');
// });
});
p.then(value => {
console.log(111);
}, reason => {
// 如果返回值是非promise类型的数据
return reason;
})
.then(value => {
console.log(value); // 'no'
// 如果返回值是promise类型的数据
return new Promise((resolve, reject) => {
resolve(333);
});
}, reason => {
// ...
})
.then(value => {
console.log('收到的值是:', value); // 333
// 如果抛出异常
throw 'error'
})
.then(() => {
// ...
}, reason => {
console.log(reason); // error
})
// 如果then中是空值,什么也不传,出现bug!!!
.then()
.then(() => {
console.log('我的上一个then是空值,我还可以输出吗?');
});
最后的测试,如果then中是空值,什么也不传,出现bug!!!
4、值的穿透
解决:
Promise.prototype.then = function(onResolved, onRejected) {
// 注意保存一下this的指向,因为下面普通函数,内部的this指向是window
const _this = this;
// 判断回调函数参数
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason;
}
}
if (typeof onResolved !== 'function') {
onResolved = value => value;
}
.....
当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这叫做值的穿透。
4、完整代码示例
写这个then方法,被这个then方法折磨了一整天了,中途踩了一些巨坑。
关于Promise的手写教程就先写到这里吧,catch、all、race、resolve、reject等方法的实现,会在下一篇博客中进行讲解。
这些方法相对then方法来说就简单得多了。
如果关于本篇文章有什么问题的话,可以私信我,谢谢阅读。
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = undefined;
// 用于保存回调函数的数组
this.callbacks = [];
// 保存实例对象的this值
const _this = this;
// 定义resolve函数
function resolve(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'fulfilled'; // fulfilled
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
// 3、调用异步任务下,成功的回调函数
setTimeout(() => {
_this.callbacks.forEach(item => {
item.Resolved(data);
});
});
}
function reject(data) {
// 判断状态,promise状态只能更改一次
if (_this.PromiseState !== 'pending') return;
// 1、修改对象的状态(promiseState)
_this.PromiseState = 'rejected'; // rejected
// 2、设置对象的结果值(promiseResult)
_this.PromiseResult = data;
// 3、调用异步任务下,失败的回调函数
setTimeout(() => {
_this.callbacks.forEach(item => {
item.Rejected(data);
});
});
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function(onResolved, onRejected) {
// 注意保存一下this的指向,因为下面普通函数,内部的this指向是window
const _this = this;
// 判断回调函数参数
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason;
}
}
if (typeof onResolved !== 'function') {
onResolved = value => value;
}
// then函数的返回值是一个新的promise对象
return new Promise((resolve, reject) => {
function handler(func) {
try {
let result = func(_this.PromiseResult);
// 如果返回值是promise对象
if (result instanceof Promise) {
result.then(v => {
resolve(v);
}, r => {
reject(r);
});
} else {
// 如果返回值是非promise对象
resolve(result);
}
} catch (e) {
// 如果抛出异常,新promise变成rejected,reason为抛出的异常。
reject(e);
}
}
// 如果实例对象上的PromiseState是fulfilled,执行onResolved
if (this.PromiseState === 'fulfilled') {
// 异步执行
setTimeout(() => {
handler(onResolved);
});
}
// 如果实例对象上的PromiseState是rejected,执行onRejected
if (this.PromiseState === 'rejected') {
// 异步执行
setTimeout(() => {
handler(onRejected);
});
}
// 如果new Promise中的函数是异步的,例如一秒之后再调用resolveo或者reject的情况
// 后续的新promise对象的状态都是pending,都会走这里。
// 我们就将回调函数保存起来,在resolve和reject方法中去调用
if (this.PromiseState === 'pending') {
this.callbacks.push({
Resolved: () => {
handler(onResolved);
},
Rejected: () => {
handler(onRejected);
}
});
}
});
}