从0到1实现一个满足Promises/A+规范的Promise
文章目录
前言
Promise
是每个前端必备的知识储备,Promise
可以解决开发中常遇见的嵌套回调,同步并发,以及多个异步处理错误的问题。我们可以用Promise
包裹异步方法使它更加优雅,同时让异步实现同步执行的async-await也是基于Promise
。理解Promise
的原理可以减少我们使用时的一些疑惑,用起来更加得心应手。
编写前需要点亮的一些知识点
- javascript基本知识(原型、闭包、类型判断…,这里就不展开详说了)
- 什么是高阶函数?
- 你知道什么是发布订阅吗?
高阶函数
一个函数返回另一个函数,或者一个函数的参数中有函数就叫做高阶函数。
在业务的开发中对于一些通用性比较强核心函数,本次业务开发中需要对其增加一些额外的逻辑,我们就可以在使用高阶函数进行包裹处理。
// 核心方法
function core(...args){
// 代码省略....
console.log('core',args);
// 代码省略....
}
// 在不更改核心代码的情况下,给core函数增加一些额外的逻辑
Function.prototype.before = function (cb) {
return (...args) =>{
cb();
this(...args);
}
}
let newCore = core.before(()=>{
console.log('before')
})
newCore('a','b');
通过高阶函数,我们实现了对函数的扩展。
发布订阅
发布订阅主要分成两个部分on和emit,其中on就是把一些函数维护到一个数组当中,emit就是让数组当中的方法依次执行,其中on(订阅)和emit(发布)并没有明显的关联。下面我们通过一个具体场景来进行说明。
现在需要分别读取两个文件,我需要在这两个文件都读取完之后再将它们的内容打印出来,那么我应该在什么时机去打印该内容?
你可能会想到用一个变量来做计数器,当计数器初始值2,当计数器为0的时候说明这两个文件都被读取完了,此时我们就可以进行相应的逻辑操作。
let fs = require('fs');
let person = {};
function after(time, callback) {
return function() {
if (--time == 0) {
callback();
}
}
}
let cb = after(2, function() {
console.log(person)
})
fs.readFile('./promise/callback/age.txt', 'utf-8', function (err, data) {
person.age = data;
cb()
})
fs.readFile('./promise/callback/name.txt', 'utf-8', function (err, data) {
person.name = data;
cb()
})
但是此时我们想做更多其他事情,我们可能就需要不断的定义新的after1,after2…以及cb1,cb2…,然后在readFile中不断调用。这样用起来就会显得比较麻烦,不够优雅。此时我们通过发布订阅模式就可以做到更加灵活,通过数组去管理我们的业务函数,on将函数放进数组,emit遍历数组,每次异步读取文件时,只调用一次emit方法即可。
let fs = require('fs');
let person = {};
let event = {
arr: [],
on: function(fn) {
this.arr.push(fn)
},
emit: function() {
this.arr.forEach(item => item())
}
}
event.on(function() {
console.log("读取数据中...")
})
event.on(function() {
if (Object.keys(person).length === 2) {
console.log(person)
}
})
fs.readFile('./promise/callback/age.txt', 'utf-8', function (err, data) {
person.age = data;
event.emit();
})
fs.readFile('./promise/callback/name.txt', 'utf-8', function (err, data) {
person.name = data;
event.emit();
})
实现一个简易的Promise
实现一个简易的Promise
,首先我们需要分析Promise
的基本用法,通过new一个Promise(执行函数),我们可以得到一个Promise
的实例promise。其中执行函数中传入两个参数,调用这个两个参数,我们可以改变promise的状态,让promise的状态从等待状态到成功/失败状态。然后我们可以在promise的then方法中通过参入两个函数,可以对成功/失败执行相应的逻辑处理。
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
const PENDING = 'pending'; // 等待
class Promise {
constructor(executor) {
this.status = PENDING; // promise默认的状态
this.value - undefined; // 成功的值
this.reason = undefined; // 失败的原因
const resolve = (value) => { // 成功resolve函数
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED; // 修改状态
}
}
const reject = (reason) => { // 失败reject函数
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED; // 修改状态
}
}
try {
executor(resolve, reject); // 立即执行
} catch(e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
通过创建一个Promise
类,在其构造函数中传入一个执行器executor,将status(promise的状态),value(成功的值),reason(失败的原因)放到Promise
的实例上去,定义成功函数resolve,和失败函数reject,由于Promise
的状态一经改变就不能变,所以在成功失败函数内部需要对status的状态判断才会去,对status的值进行相应的更新,以及对value和reason的赋值,由于Promise
执行器executor是立即执行的,所以我们在构造函数中就要让其执行,如果执行函数的时候发生了异常,用try catch捕捉执行reject。Promise
的创建我们已经完成,现在我们要实现then方法,在Promise
类内部新增一个then方法,该方法会传入两个回调函数,成功回调函数onFulfilled,失败回调函数onRejected,通过判断status当前的值,执行相应的回调函数即可。
Promise异步问题
现在我们已经实现了一个简易的promise,但是要是我们在调用Promise
的then方法的时候,status还未从pending转变为其他状态,那么该如何解决这个问题?
Promise.resolve(new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200)
}, 3000)
})).then(res => {
console.log(res)
}, err => {
console.log(err)
})
为解决该问题,我们需要在then当中对status等于pending时进行处理,这时候就需要用到发布订阅思想了,当调用then的时候如果此刻的状态status为pending,那么我们可以把当前回调函数放到相应的成功/失败回调的数组中存储起来,当状态从pending转变成fulfilled/rejected时,再将数组中的函数遍历执行。
const FULFILLED = 'fulfilled'; // 成功
const REJECTED = 'rejected'; // 失败
const PENDING = 'pending'; // 等待
class Promise {
constructor(executor) {
// 代码省略...
this.fulfilledCallback = []; // 成功回调
this.rejectedCallback = []; // 失败回调
const resolve = (value) => { // 成功resolve函数
if (this.status === PENDING) {
// 代码省略...
this.fulfilledCallback.forEach(item => {
item()
});
}
}
const reject = (reason) => { // 失败reject函数
if (this.status === PENDING) {
// 代码省略...
this.rejectedCallback.forEach(item => {
item()
});
}
}
// 代码省略...
}
// 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
then(onFulfilled, onRejected) {
if (this.status === PENDING) { // 说明then是异步的
this.fulfilledCallback.push(() => {
onFulfilled(this.value);
})
this.rejectedCallback.push(() => {
onRejected(this.reason);
})
}
// 代码省略...
}
}
灵魂then的链式调用
then的链式调用是Promise
的灵魂,同时也是实现Promise
最难的一部分。要实现then的链式调用,首先我们要对then的用法有个详细认知。
Promise
的链式调用,当调用then方法后会返回一个新的Promise
。- 当then中方法无论是成功还是失败返回的是一个普通值,不是
Promise
的情况,会作为外层下一次then的成功结果。 - 当then中方法会执行出错,会走到外层下一次then的失败结果。
- 如果then中返回的是一个
Promise
对象,此时会根据Promise
的结果来处理是走成功还是失败。
用一句话总结就是:如果返回一个普通值 (除了Promise
) 就会传递给下一个then的成功,如果返回一个失败的Promise
或者抛出异常,会走下一个then的失败。
根据then链式的用法我们很容易就能写出当then中返回值是普通值的时候的情况。
// 代码省略...
class Promise {
constructor(executor) {
// 代码省略...
}
// 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
then(onFulfilled, onRejected) {
let promise = new Promise((resolve, reject) => {
if (this.status === PENDING) { // 说明then是异步的
this.fulfilledCallback.push(() => {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error)
}
})
this.rejectedCallback.push(() => {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error)
}
})
}
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error)
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error)
}
}
})
return promise;
}
}
用一个新的Promise
将我们之前写在then中的逻辑包裹起来,然后将用变量x将回调函数的返回值存储起来,并执行promise的resolve(x),这过程中如果发生错误,利用trycatch捕获并执行promise的reject方法。最后再将promise返回即可。
解决了then中回调函数返回值是普通值的情况,现在我们需要来解决当返回值为Promise
时的情况:
// 代码省略...
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('错误'));
}
if (typeof x === 'function' || (x !== null && typeof x === 'object')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, res => {
resolvePromise(promise, res, resolve, reject);
}, rej => {
reject(rej);
})
} else {
resolve(x);
}
} catch (err) {
reject(rej);
}
} else {
resolve(x);
}
}
class Promise {
constructor(executor) {
// 代码省略...
}
// 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
then(onFulfilled, onRejected) {
let promise = new Promise((resolve, reject) => {
if (this.status === PENDING) { // 说明then是异步的
this.fulfilledCallback.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
this.rejectedCallback.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
}
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
})
return promise;
}
}
此时我们需要对回调函数的返回值x进行处理,如果是普通值的话我们直接调用promise的resolve即可。如果返回值x是Promise
对象的话,那么我需要对Promise
对象进行递归处理,当处理的值最终为普通值的时候,调用promise的resolve即可。这里我们可以把处理x的方法抽离出方法resolvePromise,分别传入promise,x,以及promise的resolve,reject四个参数,由于这里我们要使用promise,promise还没定义,所以我们需要用一个setTimeout让其异步。现在我们来多对resolvePromise进行处理,主要可以分成以下几个点:
- 判断promise === x ?是的话调用reject。
- 判断x是否是function或者x是否为object且不为null。
- 如果第2步为假则说明x是个普通值,直接调用resolve方法即可。
- 如果第2步为真则去判断x的then属性。
- 若then不为function则说明x是个普通值,直接调用resolve方法即可。
- 若then为function我们可以认为x就是
Promise
,调用then方法并使用call方法让其内部this指向x,在then的成功回调里调用resolvePromise,将第二参数替换成回调函数传入的参数即可。在then的失败回调里直接调用reject方法即可。
完成上述操作,我们基本就完成了一个符合Promises/A+规范的Promise啦~😊。
附上完整代码:
const FULFILLED = 'FULFILLED'; // 成功
const REJECTED = 'REJECTED'; // 失败
const PENDING = 'PENDING'; // 等待
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('错误'));
}
if (typeof x === 'function' || (x !== null && typeof x === 'object')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, res => {
resolvePromise(promise, res, resolve, reject);
}, rej => {
reject(rej);
})
} else {
resolve(x);
}
} catch (err) {
reject(rej);
}
} else {
resolve(x);
}
}
class Promise {
constructor(executor) {
this.status = PENDING; // promise默认的状态
this.value - undefined; // 成功的原因
this.reason = undefined; // 失败的原因
this.fulfilledCallback = []; // 成功回调
this.rejectedCallback = []; // 失败回调
const resolve = (value) => { // 成功resolve函数
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED; // 修改状态
this.fulfilledCallback.forEach(item => {
item()
});
}
}
const reject = (reason) => { // 失败reject函数
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED; // 修改状态
this.rejectedCallback.forEach(item => {
item()
})
}
}
try {
executor(resolve, reject); // 立即执行
} catch (e) {
reject(e)
}
}
// 当用户调用then方法的时候 此时promise可能为等待态,先占存起来,因为后续可能会调用resolve,reject
then(onFulfilled, onRejected) {
let promise = new Promise((resolve, reject) => {
if (this.status === PENDING) { // 说明then是异步的
this.fulfilledCallback.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
this.rejectedCallback.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
})
}
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise, x, resolve, reject)
} catch (error) {
reject(error)
}
}, 0);
}
})
return promise;
}
}
module.exports = Promise
// 测试
let MyPromise = require('./source/4promise')
let promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 3000)
console.log('开始')
})
promise.then((res) => {
console.log(res)
return new MyPromise((resolve, reject) => {
reject('失败88888')
})
}, err => {
console.log(err)
}).then((res) => {
console.log("---2res", res);
}, err => {
console.log("---2err", err);
return { ss: "jinhong" };
}).then(res => {
console.log("-------3res", res);
}, err => {
console.log("-------3err", err);
}).then(res => {
console.log("-------4res", res);
}, err => {
console.log("-------4err", err);
})
其他方法
Promise
除了基本用法,还有Promise.resolve(value),Promise.reject(value)它们可以分别创建成功值/拒绝原因的Promise
对象。以及Promise.all() 和 Promise.race() 两个并行运行异步操作的组合式工具,以及错误捕获catch方法。这些方法在我们理解了then链式调用后,实现起来就相应的变的简单了起来。
-
Promise.resolve(value)和Promise.reject(value)
这两个静态方法直接new一个
Promise
并在执行函数中调用相应的成功,失败方法即可。// 代码省略... function resolvePromise(promise, x, resolve, reject) { // 代码省略... } class Promise { // 代码省略... static resolve(value){ return new Promise((resolve,reject)=>{ resolve(value); }) } static reject(value){ return new Promise((resolve,reject)=>{ reject(value); }) } }
-
catch
catch是
Promise
原型上的方法,catch实现主要是指定回调函数,将它的回调函数当作参数传递给then的失败回调即可。// 代码省略... function resolvePromise(promise, x, resolve, reject) { // 代码省略... } class Promise { // 代码省略... catch(errorFn){ return this.then(null,errorFn); } }
-
finally
无论成功失败都会执行finally,且可以继续向下执行。
// 代码省略... function resolvePromise(promise, x, resolve, reject) { // 代码省略... } class Promise { // 代码省略... finally(cb) { return this.then((data) => { return Promise.resolve(cb()).then(() => data); }, (err) => { return Promise.resolve(cb()).then(() => { throw err }); }) } }
-
Promise.all()
Promise.all接收一个数组等待所有都成功完成,返回一个新数组,在这当中如果有一个失败,那么 Promise.all 将立即变为失败。
// 代码省略... function resolvePromise(promise, x, resolve, reject) { // 代码省略... } class Promise { // 代码省略... static all = function (promises) { return new Promise((resolve, reject) => { let result = []; let times = 0; const processSuccess = (index, val) => { result[index] = val; if (++times === promises.length) { resolve(result); } } for (let i = 0; i < promises.length; i++) { let p = promises[i]; if (p && typeof p.then === 'function') { p.then((data) => { processSuccess(i, data) }, reject) } else { processSuccess(i, p); } } }) } }
Promise.all([ new Promise((resolve, reject) => { setTimeout(() => { resolve('成功1') }, 2000); }), new Promise((resolve, reject) => { setTimeout(() => { resolve('成功2') }, 1000); }),1, 2, 3]).then(data => { console.log(data); }).catch(err => { console.log(err,'errr') })
-
Promise.race()
Promise.race()谁最先成功或失败就采用他的结果,一般可以用来进行超时处理。
// 代码省略... function resolvePromise(promise, x, resolve, reject) { // 代码省略... } class Promise { // 代码省略... static race = function (promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; i++) { let p = promises[i]; if (p && typeof p.then === 'function') { p.then(resolve, reject); // 一旦成功就直接 停止 } else { resolve(p); } } }) } }
// 测试 let p1 = new Promise((resolve,reject)=>{ setTimeout(() => { resolve('成功') }, 1000); }) let p2 = new Promise((resolve,reject)=>{ setTimeout(() => { reject('失败') }, 500); }) Promise.race([p1, p2, null, 1]).then((data) => { console.log(data); }).catch((err) => { console.log(err) });
应用
-
图片加载超时,脚本超时加载超时问题,不再采用成功的结果
var abort; let p1 = new Promise((resolve, reject) => { abort = reject; setTimeout(() => { resolve('成功'); }, 3000); }) p1.abort = abort; p1.then(data => { console.log(data); }, err => { console.log(err); }) setTimeout(() => { p1.abort("超过一秒了"); }, 1000);
这样暴露一个变量出来显得比较的low,此时我们可以利用race的特性,去new一个超时的promise。
function wrap(p) { let abort; let p1 = new Promise((resolve, reject) => { abort = reject; }) let p2 = Promise.race([p, p1]); p2.abort = abort; return p2; } let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('成功'); }, 3000); }) let p2 = wrap(p1); p2.then(data => { console.log(data); }, err => { console.log(err); }) setTimeout(() => { p2.abort("超过一秒了"); }, 1000);