动手写一个简单的promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

  根据上面的消息和定义;我们先写个简单的,看着有点不像的MyPromise;

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秒后输出

解释一下整体代码:

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

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

那用户如何调用 resolve 和 reject 呢?

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

所以 MyPromise 内部在调用 fn 时会把 resolve 和 reject当作参数传递给 fn。

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

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

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

pending、resolved、rejected 分别表示 执行中、已完成、已失败。

然后通过观察用户调用的是 resolve 还是 reject 可以判断当前Promise的状态。 那么会有三种情况:

  • 在用户调用 resolve 或 reject 之前状态是 pending
  • 用户调用 resolve 时,状态将变为 resolved
  • 用户调用 reject 时,状态将变为 rejected

下面进行代码的改造,定义了三个常量表示状态以及一个变量 state 用来存储当前状态。 并且当 resolve 被调用时将 state 修改为 resolved 。

const PEDNING="pending";//执行状态
    const RESOLVED='resolved';//以完成;
    const REJECTED='rejected';//以失败

    function MyPromise(fn){
        const that=this
        //初始状态为执行中,pending
        this.state=PEDNING;

        function resolve(value){
            console.log(value)
            that.state=RESOLVED;
        }
        function reject(err){
            console.log(err)
            that.state=REJECTED;
        }
        fn(resolve,reject);
    }

OK,现在已经能知道Promise当前所处的状态了,但是任务完了得拿到结果吧,MyPromise 的 resolve 被调用,那也只是MyPromise知道任务完成了,用户还不知道呢。 所以我们需要回调函数告诉用户,是的,其实就是回调函数。 这时候就轮到 then 方法出场了,用户通过then方法传入回调函数, MyPromise 将在成功调用 resolve 时调用用户传入的回调函数。 开始改造代码,MyPromise 内部需要变量存储回调函数,then 方法的作用就是将用户传入的回调函数赋予 MyPromise 内的变量。 所以 then 方法长这样,接收两个参数,一个是成功时的回调函数,一个是失败时的回调函数

const PEDNING="pending";//执行状态
        const RESOLVED='resolved';//以完成;
        const REJECTED='rejected';//以失败

        function MyPromise(fn){
            const that=this
            //初始状态为执行中,pending
            this.state=PEDNING;
            //两个储存回调函数的变量
            this.resolvedCallback;
            this.rejectedCallback;

            function resolve(value){
                that.state=RESOLVED;
                that.resolvedCallback && that.resolvedCallback(value); 
            }
            function reject(err){
                that.state=REJECTED;
                that.rejectedCallback && that.rejectedCallback(err);
            }
            fn(resolve,reject);
        }

        MyPromise.prototype.then=function(onFulfilled,onRejected){
            this.resolvedCallback = onFulfilled;
            this.rejectedCallback = onRejected;
        }

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

(function(){
        
        const PEDNING="pending";//执行状态
        const RESOLVED='resolved';//以完成;
        const REJECTED='rejected';//以失败

        function MyPromise(fn){
            const that=this
            //初始状态为执行中,pending
            this.state=PEDNING;
            //两个储存回调函数的变量
            this.resolvedCallback;
            this.rejectedCallback;

            function resolve(value){
                that.state=RESOLVED;
                that.resolvedCallback && that.resolvedCallback(value); 
            }
            function reject(err){
                that.state=REJECTED;
                that.rejectedCallback && that.rejectedCallback(err);
            }
            fn(resolve,reject);
        }

        MyPromise.prototype.then=function(onFulfilled,onRejected){
            this.resolvedCallback = onFulfilled;
            this.rejectedCallback = onRejected;
        }

        new MyPromise((resolve,reject)=>{
            setTimeout(()=>{
                resolve('我是结果')
            },4000);
        }).then((value)=>{
            console.log(value)
        })
    })()

通过匿名函数和函数自执行,形成局部作用域,保护里面的变量;

上面的代码,用法上已经和Promise长得差不多了,但是如果我们多次调用 then 方法呢? 是的,只有最后一个 then 方法里的回调函数能执行,这当然没法满足我们的需要。 于是,将两个回调函数改成函数数组(请回想一下前置知识),并在状态更改时遍历调用回调函数。 改造后的代码如下:

 (function(){

    const PEDNING="pending";//执行状态
        const RESOLVED='resolved';//以完成;
        const REJECTED='rejected';//以失败

        function MyPromise(fn){
            const that=this
            //初始状态为执行中,pending
            this.state=PEDNING;
            //两个储存回调函数的变量,注意,变成了数组
            this.resolvedCallbackList=[];
            this.rejectedCallbackList=[];

            function resolve(value){
                that.state=RESOLVED;
                that.resolvedCallbackList && that.resolvedCallbackList.forEach(cbFn =>cbFn(value)); 
            }
            function reject(err){
                that.state=REJECTED;
                that.rejectedCallbackList && that.rejectedCallbackList.forEach(cbFn =>cbFn(err)); 
            }
            fn(resolve,reject);
        }

        MyPromise.prototype.then=function(onFulfilled,onRejected){
            this.resolvedCallbackList.push(onFulfilled);
            this.rejectedCallbackList.push(onRejected);
             // 这里是为了链式调用,所以要返回this,即myPromise.then().then().then()...
            return this
        }

        new MyPromise((resolve,reject)=>{
            setTimeout(()=>{
                resolve('经过5秒出现')
            },5000)
        }).then((val)=>{
            console.log(val+'第一次出现的value')
        }).then((val)=>{
            console.log(val+'第二次出现的value')
        })
    })()

上面已经是简版Promise的实现了。 但是我们还可以更完善一点,增强 MyPromise 的健壮性。 例如,若用户自定义函数在执行过程中发生了错误,会中断程序的执行,于是我们增加try...catch...语句,并在发生错误时主动执行reject函数告知用户。

try {
    fn(resolve, reject);
} catch (e) {
    reject(e);
}

又或者,对参数进行校验,状态进行判断等,以 then为例,若用户传入的参数不是函数呢? 或者Promise的状态已经时rejected或resolved,此时调用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://www.cnblogs.com/thomas-yang-github/p/11845717.html