一个超详细的简版Promise实现

这是一个简版非规范的Promise实现,其中前置知识点比较重要,理解了前置知识点的内容,也就了解了Promise最基本的原理。

前置知识

这小节的核心是:把函数名当变量用。

以下三点理解之后,就能够实现简单的Promise了。


  1. 类型为Function的变量,可以像函数一样调用。

举个栗子

let myFn = function () {};
复制代码

你可以直接调用

myFn(); 
复制代码

和下面的代码效果是一样的

function myFn() {
    // code here...
}

myFn();
复制代码
  1. 函数数组,即函数里的每一个元素都是函数,可以用遍历调用。
let fnArray = [];
let fn1 = () => { console.log(1) };
let fn2 = () => { console.log(2) };
fnArray.push(fn1);
fnArray.push(fn2);
fnArray.forEach(cb => cb());
复制代码

这段代码的运行结果是

1
2
复制代码
  1. 函数可以当作参数传递
function showYouFn(fn1, fn2) {
    // 调用
    fn1();
    fn2();
}

let myFn1 = () => { console.log(3); };
let myFn2 = () => { console.log(4); };

showYouFn(myFn1, myFn2);
复制代码

回想一下:把函数名当变量用。

实现

下面是一个简陋得不像Promise的Promise实现

function MyPromise(fn) {
    function resolve(value) {
        console.log(value);
    }
    function reject(value) {
        console.log(value);
    }
    
    fn(resolve, reject);
}
复制代码

现在你就可以用上自定义的Promise了

new MyPromise((resolve, reject) => {
    setTimeout(()=>{
        resolve('你看到我啦!');
    }, 2000);
});
复制代码

将会在2秒后输出

你看到我啦!
复制代码

下面的代码效果和上面的一样,写出来是为了再次强调:函数当变量用。

let promiseFn = (resolve, reject) => {
    setTimeout(()=>{
        resolve('你看到我啦!');
    }, 2000);
};
new MyPromise(promiseFn);
复制代码

解释一下整体代码:

MyPromise 中的参数 fn 是需要用户传入的自定义函数(该函数需要接收两个参数)。

MyPromise 中的内部还有两个函数 resolvereject,其中 resolve 表示用户的异步任务成功时应该调用的函数,reject 表示用户的异步任务失败时该调用的函数。

那用户如何调用 resolvereject 呢?

很简单,把两个函数当作 fn的参数传递出去即可。

所以 MyPromise 内部在调用 fn 时会把 resolvereject当作参数传递给 fn

然后用户在自定义函数内调用 resolvereject 来通知 MyPromise 异步任务已经执行完了。


建议复制下面的代码去控制台试试

function MyPromise(fn) {
    function resolve(value) {
        console.log(value);
    }
    function reject(value) {
        console.log(value);
    }
    
    fn(resolve, reject);
}

new MyPromise((resolve, reject) => {
    setTimeout(()=>{
        resolve('你看到我啦!');
    }, 2000);
});
复制代码

通过上面的代码可以发现一个问题,我们不知道Promise的异步任务进行到哪一步了、是成功还是失败了。

所以增加三个状态用来标识一个Promise的异步任务进行到何种程度了。

pendingresolvedrejected 分别表示 执行中、已完成、已失败。

然后通过观察用户调用的是 resolve 还是 reject 可以判断当前Promise的状态。

那么会有三种情况:

  1. 在用户调用 resolvereject 之前状态是 pending
  2. 用户调用 resolve 时,状态将变为 resolved
  3. 用户调用 reject 时,状态将变为 rejected

下面进行代码的改造,定义了三个常量表示状态以及一个变量 state 用来存储当前状态。

并且当 resolve 被调用时将 state 修改为 resolved

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {

    const that = this;
    // 初始时状态为执行中,即pending
    that.state = PENDING;

    function resolve(value) {
        console.log(value);
        // resolve被调用时修改状态
        that.state = RESOLVED;
    }
    function reject(value) {
        console.log(value);
        // reject被调用时修改状态
        that.state = REJECTED;
    }
    
    fn(resolve, reject);
}
复制代码

OK,现在已经能知道Promise当前所处的状态了,但是任务完了得拿到结果吧,MyPromiseresolve 被调用,那也只是MyPromise知道任务完成了,用户还不知道呢。

所以我们需要回调函数告诉用户,是的,其实就是回调函数。

这时候就轮到 then 方法出场了,用户通过then方法传入回调函数, MyPromise 将在成功调用 resolve 时调用用户传入的回调函数。

开始改造代码,MyPromise 内部需要变量存储回调函数,then 方法的作用就是将用户传入的回调函数赋予 MyPromise 内的变量。

所以 then 方法长这样,接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数。

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

function MyPromise(fn) {
    const that = this;
    that.state = PENDING;
    
    // 两个存储回调函数的变量
    that.resolvedCallback;
    that.rejectedCallback;

    function resolve(value) {
        console.log(value);
        that.state = RESOLVED;
        // 调用用户的回调函数,并告知结果
        that.resolvedCallback && that.resolvedCallback(value);
    }
    function reject(value) {
        console.log(value);
        that.state = REJECTED;
        // 调用用户的回调函数,并告知结果
        that.rejectedCallback && that.rejectedCallback(value);
    }
    
    fn(resolve, reject);
}


MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const that = this;
    that.resolvedCallback = onFulfilled;
    that.rejectedCallback = onRejected;
}
复制代码

是的,一个简版Promise几乎大功告成,让我们再试试在浏览器执行如下代码(注意我删除了 resolvereject 里的console语句)

(function () {
    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';
    function MyPromise(fn) {
        const that = this;
        that.state = PENDING;
        
        // 两个存储回调函数的变量
        that.resolvedCallback;
        that.rejectedCallback;
    
        function resolve(value) {
            that.state = RESOLVED;
            // 调用用户的回调函数,并告知结果
            that.resolvedCallback && that.resolvedCallback(value);
        }
        function reject(value) {
            that.state = REJECTED;
            // 调用用户的回调函数,并告知结果
            that.rejectedCallback && that.rejectedCallback(value);
        }
        
        fn(resolve, reject);
    }
    
    
    MyPromise.prototype.then = function(onFulfilled, onRejected) {
        const that = this;
        that.resolvedCallback = onFulfilled;
        that.rejectedCallback = onRejected;
    }
    
    new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('你又看到我啦~');
        }, 2000);
    }).then(value => {
        console.log(value);
    });
})();
复制代码

上面的代码,用法上已经和Promise长得差不多了,但是如果我们多次调用 then 方法呢?

是的,只有最后一个 then 方法里的回调函数能执行,这当然没法满足我们的需要。

于是,将两个回调函数改成函数数组(请回想一下前置知识),并在状态更改时遍历调用回调函数。

改造后的代码如下:

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
function MyPromise(fn) {
    const that = this;
    that.state = PENDING;
    
    // 注意这里变成了两个数组
    that.resolvedCallbackArray = [];
    that.rejectedCallbackArray = [];

    function resolve(value) {
        that.state = RESOLVED;
        // 遍历用户的回调函数,告知结果
        that.resolvedCallbackArray.forEach(cbFn => cbFn(value));
    }
    function reject(value) {
        that.state = REJECTED;
        // 遍历用户的回调函数,告知结果
        that.rejectedCallbackArray.forEach(cbFn => cbFn(value));
    }
    
    fn(resolve, reject);
}
    
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const that = this;
    that.resolvedCallbackArray.push(onFulfilled);
    that.rejectedCallbackArray.push(onRejected);
    
    // 这里是为了链式调用,即myPromise.then().then().then()...
    return that;
}
复制代码

现在我们可以多次调用 then 方法了。试试在控制台运行下面的代码:

(function () {
    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';
    function MyPromise(fn) {
        const that = this;
        that.state = PENDING;
        
        // 注意这里变成了两个数组
        that.resolvedCallbackArray = [];
        that.rejectedCallbackArray = [];
    
        function resolve(value) {
            that.state = RESOLVED;
            // 遍历用户的回调函数,告知结果
            that.resolvedCallbackArray.forEach(cbFn => cbFn(value));
        }
        function reject(value) {
            that.state = REJECTED;
            // 遍历用户的回调函数,告知结果
            that.rejectedCallbackArray.forEach(cbFn => cbFn(value));
        }
        
        fn(resolve, reject);
    }
        
    MyPromise.prototype.then = function(onFulfilled, onRejected) {
        const that = this;
        that.resolvedCallbackArray.push(onFulfilled);
        that.rejectedCallbackArray.push(onRejected);
        return that;
    }
    
    new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('你又看到我啦~');
        }, 2000)
    }).then(value => {
        console.log('第一次', value);
    }).then(value => {
        console.log('第二次', value);
    });
})();
复制代码

上面已经是简版Promise的实现了。

但是我们还可以更完善一点,增强 MyPromise 的健壮性。

例如,若用户自定义函数在执行过程中发生了错误,会中断程序的执行,于是我们增加try...catch...语句,并在发生错误时主动执行reject函数告知用户。

try {
    fn(resolve, reject);
} catch (e) {
    reject(e);
}
复制代码

又或者,对参数进行校验,状态进行判断等,以 then为例,若用户传入的参数不是函数呢? 或者Promise的状态已经时rejectedresolved,此时调用then呢?

改造 then 后代码如下:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    if(typeof onRejected !== 'function') {
        onRejected = v => v;
    }
    if(typeof onFulfilled !== 'function') {
        onFulfilled = v => { throw r };
    }
    const that = this;
    if (that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallbacks.push(onRejected)
    }
    if (that.state === RESOLVED) {
        onFulfilled(that.value)
    }
    if (that.state === REJECTED) {
        onRejected(that.value)
    }
}
复制代码

还有其他方法在理解了基本原理之后可以自己根据规范改造,本文到此告一段落,感谢阅读。

转载于:https://juejin.im/post/5d2470a46fb9a07eea32986b

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值