手把手教你实现一个完整的 Promise es6学习笔记5--promise大白话讲解Promise一

用过 Promise,但是总是有点似懂非懂的感觉,也看过很多文章,还是搞不懂 Promise的 实现原理,后面自己边看文章,边调试代码,终于慢慢的有感觉了,下面就按自己的理解来实现一个 Promise。

 

已将每一步的代码都放在了 github 上,方便大家阅读。如果觉得好的话,欢迎star。

想要完全理解代码,需要理解 this 和闭包的含义

 

Promise是什么

简单来说,Promise 主要就是为了解决异步回调的问题。用 Promise 来处理异步回调使得代码层次清晰,便于理解,且更加容易维护。其主流规范目前主要是 Promises/A+ 。对于 Promise 用法不熟悉的,可以参看我的这篇文章——es6学习笔记5--promise,理解了再来看这篇文章,会对你有很大帮助的。

在开始前,我们先写一个 promise 应用场景来体会下 promise 的作用。目前谷歌和火狐已经支持 es6 的 promise。我们采用 setTimeout 来模拟异步的运行,具体代码如下:

function fn1(resolve,reject) {
    setTimeout(() {
        console.log('步骤一:执行');
        resolve('1');
    },500);
}

 fn2(resolve,1)">() {
        console.log('步骤二:执行');
        resolve('2'new Promise(fn1).then((val){
    console.log(val);
    return new Promise(fn2);
}).then(return 33;
}).then((val){
    console.log(val);
});

最终我们写的promise同样可以实现这个功能。

初步构建

下面我们来写一个简单的 promsie。Promise 的参数是函数 fn,把内部定义 resolve 方法作为参数传到 fn 中,调用 fn。当异步操作成功后会调用 resolve 方法,然后就会执行 then 中注册的回调函数。

 Promise(fn){
  //需要一个成功时的回调
  var callback;
  一个实例的方法,用来注册异步事件
  this.then = (done){
    callback = done;
  }
   resolve(){
    callback();
  }
  fn(resolve);
}

加入链式支持

下面加入链式,成功回调的方法就得变成数组才能存储。同时我们给 resolve 方法添加参数,这样就不会输出 undefined。

 Promise(fn) {
    var promise = this,value = null;
        promise._resolves = [];

     (onFulfilled) {
        promise._resolves.push(onFulfilled);
        ;
    };

     resolve(value) {
        promise._resolves.forEach( (callback) {
            callback(value);
        });
    }

    fn(resolve);
}
  • promise = this, 这样我们不用担心某个时刻 this 指向突然改变问题。

  • 调用 then 方法,将回调放入 promise._resolves 队列;

  • 创建 Promise 对象同时,调用其 fn,并传入 resolve 方法,当 fn 的异步操作执行成功后,就会调用 resolve ,也就是执行promise._resolves列中的回调;

  • resolve 方法 接收一个参数,即异步操作返回的结果,方便传值。

  • then方法中的 return this 实现了链式调用。

但是,目前的 Promise 还存在一些问题,如果我传入的是一个不包含异步操作的函数,resolve就会先于 then 执行,也就是说 promise._resolves 是一个空数组。

为了解决这个问题,我们可以在 resolve 中添加 setTimeout,来将 resolve 中执行回调的逻辑放置到 JS 任务队列末尾。

     resolve(value) {
        setTimeout(() {
            promise._resolves.forEach( (callback) {
                callback(value);
            });
        },0);
    }

引入状态

剖析 Promise 之基础篇 说 这里存在一点问题: 如果 Promise 异步操作已经成功,之后调用 then  注册的回调再也不会执行了,而这是不符合我们预期的。

对于这句话不是很理解,有知道的可以留言说下,最好能给实例说明下。但我个人觉得是,then 中的注册的回调都会在 resolve 运行之前就添加到数组当中,不会存在不执行的情况啊。

接着上面的步伐,引入状态:

 [];
        promise._status = 'PENDING';

     (onFulfilled) {
        if (promise._status === 'PENDING') {
            promise._resolves.push(onFulfilled);
            ;
        }
        onFulfilled(value);
        return this;
    };


     resolve(value) {
        setTimeout((){
            promise._status = "FULFILLED";
            promise._resolves.forEach( (callback) {
                callback(value);
            })
        },0);
    }

    fn(resolve);
}

每个 Promise 存在三个互斥状态:pending、fulfilled、rejected。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

加上异步结果的传递

目前的写法都没有考虑异步返回的结果的传递,我们来加上结果的传递:

     resolve(value) {
        setTimeout((){
            promise._status = "FULFILLED";
            promise._resolves.forEach( (callback) {
                value = callback(value);
            })
        },1)">);
    }

串行 Promise

串行 Promise 是指在当前 promise 达到 fulfilled 状态后,即开始进行下一个 promise(后邻 promise)。例如我们先用ajax从后台获取用户的的数据,再根据该数据去获取其他数据。

这里我们主要对 then 方法进行改造:

     (onFulfilled) {
        new Promise((resolve) {
            function handle(value) {
                var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
                resolve(ret);
            }
            if (promise._status === 'PENDING') {
                promise._resolves.push(handle);
            } else if(promise._status === FULFILLED){
                handle(value);
            }
        })
        
    };

then 方法该改变比较多啊,这里我解释下:

  • 注意的是,new Promise() 中匿名函数中的 promise (promise._resolves 中的 promise)指向的都是上一个 promise 对象, 而不是当前这个刚刚创建的。

  • 首先我们返回的是新的一个promise对象,因为是同类型,所以链式仍然可以实现。

  • 其次,我们添加了一个 handle 函数,handle 函数对上一个 promise 的 then 中回调进行了处理,并且调用了当前的 promise 中的 resolve 方法。

  • 接着将 handle 函数添加到 上一个promise 的 promise._resolves 中,当异步操作成功后就会执行 handle 函数,这样就可以 执行 当前 promise 对象的回调方法。我们的目的就达到了。

有些人在这里可能会有点犯晕,有必要对执行过程分析一下,具体参看以下代码:

fn1).then(fn2).then(fn3)})

fn1,fn2,fn3的函数具体可参看最前面的定义。

  1. 首先我们创建了一个 Promise 实例,这里叫做 promise1;接着会运行 fn1(resolve);

  2. 但是 fn1 中有一个 setTimeout 函数,于是就会先跳过这一部分,运行后面的第一个 then 方法;

  3. then 返回一个新的对象 promise2, promise2 对象的 resolve 方法和 then 方法的中回调函数 fn2 都被封装在 handle 中, 然后 handle 被添加到   promise1._resolves 数组中。

  4. 接着运行第二个 then 方法,同样返回一个新的对象 promise3,包含 promise3 的 resolve 方法和 回调函数 fn3 的 handle 方法被添加到 promise2._resolves 数组中。

  5. 到此两个 then 运行结束。 setTimeout 中的延迟时间一到,就会调用 promise1的 resolve方法。

  6. resolve 方法的执行,会调用 promise1._resolves 数组中的回调,之前我们添加的 handle 方法就会被执行; 也就是 fn2 和 promsie2 的 resolve 方法,都被调用了。

  7. 以此类推,fn3 会和 promise3 的 resolve 方法 一起执行,因为后面没有 then 方法了,promise3._resolves 数组是空的

  8. 至此所有回调执行结束

 但这里还存在一个问题,就是我们的 then 里面函数不能对 Promise 对象进行处理。这里我们需要再次对 then 进行修改,使其能够处理 promise 对象。

(resolve) {
             handle(value) {
                var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value;
                if( ret && typeof ret ['then'] == 'function'){
                    ret.then(function(value){
                       resolve(value);
                    });
                } else {
                    resolve(ret);
                }
            }
             FULFILLED){
                handle(value);
            }
        })
        
    };

在 then 方法里面,我们对 ret 进行了判断,如果是一个 promise 对象,就会调用其 then 方法,形成一个嵌套,直到其不是promise对象为止。同时 在 then 方法中我们添加了调用 resolve 方法,这样链式得以维持。

失败处理

异步操作不可能都成功,在异步操作失败时,标记其状态为 rejected,并执行注册的失败回调。

有了之前处理 fulfilled 状态的经验,支持错误处理变得很容易。毫无疑问的是,在注册回调、处理状态变更上都要加入新的逻辑:

     (onFulfilled(resolve.......
            }
            function errback(reason){
                reason = isFunction(onRejected) && onRejected(reason) || reason;
                reject(reason);
            }
            ) {
                promise._resolves.push(handle);
                promise._rejects.push(errback);
            } if(promise._status === 'FULFILLED'){
                handle(value);
            } else if(promise._status === 'REJECTED') {
              errback(promise._reason);
        }
        })
        
    };

    function reject(value) {
        setTimeout(function(){
            promise._status = "REJECTED";
            promise._rejects.forEach(function (callback) {
                promise._reason = callback( value);
            })
        },0);
    }

添加Promise.all方法

Promise.all 可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。

具体代码如下:

Promise.all = (promises){
    if (!Array.isArray(promises)) {
        throw new TypeError('You must pass an array to all.');
      }
    // 返回一个promise 实例
(resolve,reject){ var i = 0 [],len = promises.length,  count = len;
      // 每一个 promise 执行成功后,就会调用一次 resolve 函数
resolver(index) {   (value) {    resolveAll(index,value);   };  } rejecter(reason){ reject(reason); } resolveAll(index,value){
       存储每一个promise的参数
result[index] = value;
       等于0 表明所有的promise 都已经运行完成,执行resolve函数
if( --count == 0){ resolve(result) } }       依次循环执行每个promise for (; i < len; i++) {
         // 若有一个失败,就执行rejecter函数
promises[i].then(resolver(i),rejecter); } }); }

Promise.all会返回一个 Promise 实例,该实例直到参数中的所有的 promise 都执行成功,才会执行成功回调,一个失败就会执行失败回调。

日常开发中经常会遇到这样的需求,在不同的接口请求数据然后拼合成自己所需的数据,通常这些接口之间没有关联(例如不需要前一个接口的数据作为后一个接口的参数),这个时候  Promise.all 方法就可以派上用场了。

添加Promise.race方法

该函数和 Promise.all 相类似,它同样接收一个数组,不同的是只要该数组中的任意一个 Promise 对象的状态发生变化(无论是 resolve 还是 reject)该方法都会返回。我们只需要对 Promise.all 方法稍加修改就可以了。

Promise.race = Array.isArray(promises)) {
        new TypeError('You must pass an array to race.');
    }
    return Promise( promises.length;

         resolver(value) {
            resolve(value);
        }

        ) {
            promises[i].then(resolver,rejecter);
        }
    });
}

代码中没有类似一个 resolveAll 的函数,因为我们不需要等待所有的 promise 对象状态都发生变化,只要一个就可以了。

添加其他API以及封装函数

到这里,Promise 的主要API都已经完成了,另外我们在添加一些比较常见的方法。也对一些可能出现的错误进行了处理,最后对其进行封装。

完整的代码如下:

((window,undefined){

 resolve 和 reject 最终都会调用该函数
var final = (status,value){
    if(promise._status !== 'PENDING') ;
    
     所以的执行都是异步调用,保证then是先执行的
    setTimeout((){
        promise._status = status;
        st = promise._status === 'FULFILLED'
        queue = promise[st ? '_resolves' : '_rejects'];

        while(fn = queue.shift()) {
            value = fn.call(promise,value) || value;
        }

        promise[st ? '_value' : '_reason'] = value;
        promise['_resolves'] = promise['_rejects'] = undefined;
    });
}


参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve 和 reject
var Promise = (resolver){
    if (!(typeof resolver === 'function' ))
        new TypeError('You must pass a resolver function as the first argument to the promise constructor');
    如果不是promise实例,就new一个
    if(!(this instanceof Promise))  Promise(resolver);

    ;
    promise._value;
    promise._reason;
    promise._status = 'PENDING';
    存储状态
    promise._resolves = [];
    promise._rejects = [];
    
    //
    var resolve = (value) {
        由於apply參數是數組
        final.apply(promise,['FULFILLED'].concat([value]));
    }

    var reject = (reason){
        final.apply(promise,['REJECTED'].concat([reason]));
    }
    
    resolver(resolve,reject);
}

Promise.prototype.then = (onFulfilled,onRejected){
     每次返回一个promise,保证是可thenable的
     handle(value) {
             這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve
             value;

            判断是不是promise 对象
            if (ret && typeof ret ['then'] == 'function') {
                ret.then((value) {
                    resolve(value);
                },(reason) {
                    reject(reason);
                });
            } else {
                resolve(ret);
            }
        }

         errback(reason){
            reason = typeof onRejected === 'function' && onRejected(reason) || reason;
            reject(reason);
        }

        if(promise._status === 'PENDING'){
            promise._resolves.push(handle);
            promise._rejects.push(errback);
        }if(promise._status === FULFILLED){  状态改变后的then操作,立刻执行
            callback(promise._value);
        } REJECTED){
            errback(promise._reason);
        }
    });
}

Promise.prototype.catch = (onRejected){
    .then(undefined,onRejected)
}

Promise.prototype.delay = (ms,1)">this.then((ori){
        return Promise.delay(ms,value || ori);
    })
}

Promise.delay = (){
            resolve(value);
            console.log('1');
        },ms);
    })
}

Promise.resolve = (arg){
    =  len
            
        这里与race中的函数相比,多了一层嵌套,要传入index
         resolver(index) {
          (value) {
            resolveAll(index,value);
          };
        }

         value;
            ){
                resolve(result)
            }
        }

        ) {
            promises[i].then(resolver(i),rejecter);
        }
    });
}

Promise.race =  Promise;

})(window);
View Code

 

下载完整版代码,点击 github ,如果觉得好的话,欢迎star。

 

代码写完了,总要写几个实例看看效果啊,具体看下面的测试代码:

var getData100 = (){
    (){
            resolve('100ms');
    });
}

var getData200 = (){
            resolve('200ms');
    });
}
var getData300 = (){
            reject('reject');
    });
}

getData100().then((data){
    console.log(data);       100ms
     getData200();
}).then( 200ms
     getData300();
}).then((data){
    console.log(data);      
},1)"> 'reject'
});

Promise.all([getData100(),getData200()]).then( [ "100ms","200ms" ]
});

Promise.race([getData100(),getData200(),getData300()]).then( 100ms
});
Promise.resolve('resolve').then('resolve'
})
Promise.reject('reject函数').then((data){
    console.log(data);
},1)">(data){
    console.log(data);     'reject函数'
})

 

参考文章:

1、教你一步一步实现一个Promise - 飞魚

2、剖析 Promise 之基础篇

3、Promise简单实现(正常思路版)

4、大白话讲解Promise(一)

5、Javascript 中的神器——Promise

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


原文连接:https://www.cnblogs.com/dupd/p/5951311.htmlES6之前已经出现了js模块加载的方案,最主要的是CommonJS和AMD规范。commonjs主要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。同时还有CMD规范,为同步加载方案如seaJS。ES6在语言规格的层面上,实现了模块功能,而且...
以为Es6,javascript第一次支持了module。ES6的模块化分为导出(export)与导入(import)两个模块,其中在项目中,我们会经常看到一种用法import * as obj from,这种写法是把所有的输出包裹到obj对象里。示例一 1 2 3 4 5 6 7 // index.js export function fn1(data){ console.log(1) } export f.
视频讲解关于异步处理,ES5的回调使我们陷入地狱,ES6的Promise使我们脱离魔障,终于、ES7的async-await带我们走向光明。今天就来学习一下 async-await。async-await和Promise的关系经常会看到有了 async-await、promise 还有必要学习吗、async await优于promise的几个特点,接收了这些信息后,就蒙圈了。现在才知道...
TypeScript什么是TypeScript?TypeScript是由微软开发的一款开源的编程语言TypeScript是JavaScript的超集,遵循最新的ES5 ES6规范,TypeScript扩展了JavaScript的语法TypeScript更像后端 Java c# 这样的面向对象语言可以让js开发大型企业项目谷歌也在大力支持TypeScript的推广,React ,VUE 都集成了TypeScriptTypeScript安装安装-- 安装npm install -g type
export class AppComponent { title = 'Tour of heroes'; hero: Hero = { id: 1, name: '张三' };}export class Hero { id: number; name: string;}就是这一段,看起来有点晕,这里是实例化一个Hero类型的对象hero,还是创建一个变量?后面是赋值,但是不知道什么意思?hero: Hero = { id: 1, na.
用 async/await 来处理异步昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简洁,同时async/await 已经被标准化,是时候学习一下了。先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。 写一个async 函数async function timeout() {return 'hello world'
es6的语法已经出了很长的时间了,在使用上也可以通过babel这类的编译工具转译为浏览器可以识别的es5的语法,但是依旧有很多开发在写代码的时候,依旧没有用es6的语法,而是习惯使用老的语法,这篇文章主要会介绍解构赋值基本用法以及在实际使用场景中相比es5语法的优势,让大家从根本上了解es6语法的优势基本用法数组解构让我们一起先来看数组解构的基本用法:let [a, b, c] ...
参考链接1 参考链接2写法1 - 使用 function 关键字function greeter(fn: (a: string) =&gt; void) { fn("Hello, World");}function printToConsole(s: string) { console.log(s);}greeter(printToConsole);(a: string) =&gt; void上述语法的含义:表示一个函数,接收一个字符串作为输入参数,没有返回参数。可
ES6简介-ECMAScript是javascript标准-ES6就是ECMAScript的第6个版本-ECMAScript6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。ES6新增加的功能:1.let
ES6  在ES5的基础上增加了一些新的特性和写法,特别在函数写法上,增加了箭头函数 1.正经的函数写法//普通的传递值的写法functionsum1(x,y){returnx+y;}constres=sum1(2,3);console.log(res);//传递对象的方式,调用时需要传递一个对象过去function
ES5及ES6es表示ECMASCript,他是从es3,es5,es6,es5是2009.12月发布的,es6是2015.6月发布的。vue2完全支持es5的(vue3完全支持es6的),react完全支持es6es5的新特性严格模式(对应的相反的称为怪异模式)'usestrict'//一般用于相关的设计上面书写一个严格模式底下的代码就需要按照严格模
ES5的严格模式所谓严格模式,从字面上就很好理解,即更严格的模式,在这种模式下执行,浏览器会对JS的要求更苛刻,语法格式要求更细致,更符合逻辑。怪异模式:就是我们之前一直使用的开发模式,就叫怪异模式。因为很多时候出来的结果是非常怪异的,所以才称之为怪异模式。'usestrict'//一般用
相同点export与exportdefault均可用于导出常量、函数、文件、模块可在其它文件或模块中通过import+(常量|函数|文件|模块)名的方式,将其导入,以便能够对其进行使用不同点一、在一个文件或模块中,export、import可以有多个,exportdefault仅有一个//model.jsle
24.class类 25.class中的extend 26.super关键字 27.super应用 28.class属性 30.静态成员和实例成员 31.构造函数问题 32.构造函数原型 33.原型链 34.js查找机制 35.原型对象中this指向 36.扩展内置对象方法 37.call方法 38.借用父构造函数
什么是ES6ES的全称是ECMAScript,它是由ECMA国际标准化组织,制定的一项脚本语言的标准化规范。泛指2015年发布的es2015版极其后续版本ES6中新增语法letES6中新增的用于声明变量的关键字。注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特
命名修饰符let:不能重复声明变量、块级作用域leta=1;leta=2;//报错const:初始化常量,必须给初始值,否则报错、在同一个作用域内,const定义的常量不能修改其值、块级作用域consta=10a=100//报错,不能重复声明解构constobj={name:'jack'age:18sex:'
ES6新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。1{2leta=10;3varb=1;4}56a//ReferenceError:aisnotdefined.7b//1上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用
<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><metahttp-equiv="X-UA-Compatib
一,RegExp构造函数es5中,RegExp构造函数的参数有两种情况。1,参数是字符串,第二个参数表示正则表达式的修饰符(flag)。2,参数是一个正则表达式,返回一个原有正则表达式的拷贝。es6中,如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而
一、字符的Unicode表示法JavaScript允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的Unicode码点。表示法只限于码点在\u0000~\uFFFF之间的字符,超过该范围需要用两个双字节表示ES6改进:将码点放入大括号,就能正确解读该字符。转换参考:https://blog.csdn.net/hezh1994/ar