• 动手写一个简单的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)
        }
    }
  • 相关阅读:
    象限极角排序
    并查集与二部图
    POJ 1743 后缀数组不重叠最长重复子串
    POJ 2104 【主席树】【区间第K大】
    两串前缀最长后缀
    Codeforces 450D Jzzhu and Cities [heap优化dij]
    Codeforces 119C DP
    HDU 3068 [最长回文子串]
    Codeforces 132E Bits of merry old England 【最小费用最大流】
    ZOJ 3911Prime Query [素数处理 + 线段树]
  • 原文地址:https://www.cnblogs.com/thomas-yang-github/p/11845717.html
Copyright © 2020-2023  润新知