【Promise】1-简单用法 及 原理


promise 有哪些优缺点?

优点:
1.可以解决异步嵌套的问题; 2.可以解决多个异步并发问题
缺点
1.promise 基于回调的(要不停的写函数) 2.promise 无法终止异步

promise 是承诺的意思,promise是一个类,这个类上有3个状态等待态(默认)pending,成功态fulfilled,失败态rejected;一旦成功了就不能失败,失败了也不能成功

promise 特点:

1).promise是一个类, 这个类上有3个状态:等待态(默认),成功态,失败态;一旦成功了就不能失败,失败了也不能成功 resolve 代表成功; reject 代表失败
2). 每个promise 实例 都有一个then方法,then方法里有2个参数(方法):成功后走的逻辑 和 失败后走的逻辑;
3).如果new Promise的时候 报错了, 会变成失败态(抛错也算失败)

1.promise 简单用法

let promise = new Promise( (resolve , reject) => {   //Promise 是一个类,参数传一个函数,这个函数叫做 executor 执行器,执行器会立刻执行
    // 执行器会带有2个参数:resolve -成功 ; reject - 失败
    console.log(1);
    // throw new Error('失败');   //throw 语句抛出一个错误。用于:创建自定义错误
    reject(222)
    // resolve('nihao')
}).then( data => {      //每个promise 实例 都有一个then方法,then方法里有2个参数(方法):成功后走的逻辑 和 失败后走的逻辑;
    //成功走这部分
    console.log(data);    
}, err => {
    //失败走这部分
    console.log(err,'111');    
})

2.Promise 实现原理

// 因为状态常用,所以放在常量里
// 这里面的方法分为:实例上的方法,私有方法 和原型上的方法
// 一般写类 就是面向对象,写函数就是面向过程
const PENDING = 'PENDIND';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';

class Promise{
    constructor(executor){  //new Promise的时候会传一个函数,这个函数就是executor,执行器,
        //new Promise 就是用构造函数 或者constructor 构造实例
        this.state = PENDING ;  //默认是等待态, 在构造器里的属性,每个函数都有自己的属性(每个实例的值不同-独立),且可以在原型的方法上使用
        this.value = undefined;  //成功的值
        this.reason = undefined;  //失败的原因

        this.onResolvedCallbaks = [];  //成功的回调函数的数组
        this.onRejectedCallbacks = [];  //失败的回调函数的数组

        // 构造函数executor里有两个函数参数,这两个函数不需要在then方法里拿到,且属于当前构造函数里,所以在构造器里定义
        
        //这里 resolve 和 reject方法就是私有方法,在类外面找不到这个方法,不能通过事件调用
        let resolve = (value) => {    //这里用箭头函数 可以避免this 指向问题,不然以后的 resolve()里的this 可能指向window           
            //成功函数,resolve 的时候要传一个参数:成功的值,但是这个参数我们要在then 方法里使用
            // 所以我们要把这个参数也存在构造函数/构造器里
            if(this.state === PENDING){  //状态为PENDING才能改指 改状态
                this.value = value;  //一调用resolve方法酒吧 调用时传的参数 存给构造函数构造的实例里了
                this.state = RESOLVED;  //成功后修改状态
                this.onResolvedCallbaks.forEach( fn => fn()); //执行then时存起来的方法,在执行器走完后执行
            }
        }
        let reject = (reason) => {
            //失败函数,reject的时候要传一个参数:失败的原因,要在then 方法里使用这个参数
            if(this.state === PENDING){ 
                this.reason = reason ;
                this.state = REJECTED;  //失败后修改状态
                this.onRejectedCallbacks.forEach( fn => fn());
            }
        }
        try{
            executor(resolve , reject ) ; //执行器会立刻执行,一new 就执行了,我们把成功函数 和 失败函数当作参数 传给执行器
        // 这是构造函数里
        }catch(e){
            reject(e);  //如果执行 executor方法时报错了,就执行reject方法;
        }        
    }
    // 1.看这个属性能否在原型上使用
    // 2.看属性是否公用
    // then 里面要用到状态, 且每个实例要有自己的状态,不能放在公用上,所以就把状态放在自己的构造函数中constructor里
    
    then(onfulfilled,onrejected){    // then方法有2个 方法参数

        // 同步
        // then方法每个实例都有,肯定是在原型上的,也就是Promise.prototype.then = function(){}
         if(this.state === RESOLVED){ //成功的时候执行第一个参数
            onfulfilled(this.value);  //执行第一个方法参数,传一个参数,参数是在new promise是执行器立即行,执行成功后传的值
         }
         if(this.state ===REJECTED){
            onrejected(this.reason)
         }

        //  异步情况
         // 如果是异步的  就先订阅好
         if(this.state === PENDING){  //当执行then()但状态不是成功也不是失败,是等待说明还没执行完,可以先把方法存到数组里
            //等执行完再调用这些方法
            // this.onResolvedCallbaks.push(onfulfilled);  这样写可以,但是没办法在里面添加一些自己的方法了
            this.onResolvedCallbaks.push( () => {   //我们可以这样写返回一个函数,在这个函数里调用执行 方法,还可以加自己的逻辑
                // todo...
                onfulfilled(this.value);
            })
            this.onRejectedCallbacks.push(() => {   //then 的时候 new Promise()没有执行完,把方法存到数组里,等执行完在执行,
                // 应该是执行器里的resolve  和 reject方法里执行这些方法
                // todo...
                onrejected(this.reason);
            });

         }
    }
}
module.exports = Promise;

3. promise里then的‘发布订阅’

promise 还有个发布订阅的功能:一个promise 可以then()多次,如果new的时候 执行器里面 失败了,则每个then方法里都走onredected 方法

promise里时异步的调用resolve 和reject的。就是当走到then的收,new Promise()实际还每走完,此时promise状态不是成功态 也不是失败态 而是等待态
此时我们就把多个then方法里,成功要执行和函数参数 和 失败要执行的函数参数 分别存到一起,等注册完 再执行这个方法 。这个过程就和之前说的发布订阅 很类似,这个实现原理,再第2小结里有写

let promise = new Promise( (resolve , reject) => {   //Promise 执行器
    // throw new Error('失败');     //
    setTimeout(()=>{   //这里是异步,可能走到下面then的时候,这里还没有走完,没有状态
        reject(333)
    },100)
})

promise.then( data => {     
    //成功走这部分
    console.log(data);    
}, err => {
    //失败走这部分
    console.log(err,'111');    
});

promise.then( data => {     
    //成功走这部分
    console.log(data);    
}, err => {
    //失败走这部分
    console.log(err,'111');    
});

promise.then( data => {     
    //成功走这部分
    console.log(data);    
}, err => {
    //失败走这部分
    console.log(err,'111');    
});

4. promise里then的用法

目标:要分段读取, 第一个文件的输出,是下一个文件的输入

1.小白写法:

如下写法的缺点:1.回调嵌套问题,不好维护 2. 如果有err 要在每个函数里嵌套一层:异步错误处理问题,不能统一


let fs = require('fs');

fs.readFile('./name.txt','utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){   //第一个参数写data,  是上一个文件的输出
        console.log(data);
    })
})

2.我们可以写一个方法 把它变成promise

let fs = require('fs');

function read(url) {
    return new Promise((resolve, reject) => {     //resolve , reject 这是两个回调,执行器执行完调用其中之一,并传参数:值或原因
        fs.readFile(url, 'utf8', function (err, data) {   //第一个参数写data,  是上一个文件的输出
            if (err) reject(err);  //如果失败了就调用 方法reject()
            resolve(data);  //否则成功了调用resolve()
        })
    })
}
// new Promise() 构造一个实例对象,里面的执行器是new 时立即执行,然后根据成功或失败,调用resolve /reject方法改变实例对象的状态,和值或原因

read('./name.txt').then( data => {   //这里 read() 被我们写成了一个promise 实例 ,它有then方法

    read(data ).then( data => {   // name.txt 文件里放发是'./age.txt',读取后返回的是我们需要的
        console.log(data);
    },err => {
        console.log(err);
    })
},err => {
    console.log(err);
})

如果一个promise的then方法中的函数(成功或失败)返回的结果是一个promise的话,
会自动将这个promise执行,并且采用它的状态,如果成功了 会将成功的结果向外层的下一个then 传递

也就是说:里面的结果会决定走外层的then方法里的成功 或 失败

3.返回的promise 是成功态

read('./name.txt').then( data => {   
    return read(data);   //这里返回的是一个promise,会自动将这个promise执行,要返回,要有return
    // 1.并采用它的状态,如果成功了会将成功的结果向外层的 下一个then 传递 
},err => {
console.log(err);
}).then( data => {   //这里 read() 被我们写成了一个promise 实例 ,它有then方法
    console.log(data +'222');  //2.这里的data是上一个then 返回的数据值
},err => {
console.log(err);
})

上面代码中:第一个then里返回一个promise,这个promise 成功了,所以这个promise外层的then,也就是第二个then 也走成功逻辑的代码;

4.返回的promise是失败态

read('./name.txt').then(data => {
    return read(data + '111');   //1.这个返回的promise 会走reject,那下一个then 就走onrejected
    console.log(err);
}).then(data => {  
    console.log(data + '222');  
}, err => {
    console.log(err);   //2.  走这里
    return undefined; //3.  如果失败了,走error,返回的是一个普通值,那么会将这个普通值作为下一次的成功结果
    // 或者false等普通值,只要不是promise都可以
}).then(data => {   // 4.  上一个then 失败是返回的undefined,这次就走成功
    console.log(data + '222');  //这里的data是上次返回的undefined
    // 5.  我们希望走完这个then 就不要再走以后的then了,就放一个空的promise,
    // 放值 或者 抛错都会走下面的成功或失败,就只能放promise
    return new Promise( ()=>{})  //5. 终止promise,可以返回一个pending的promise
}, err => {
    console.log(err);
}).then(data => {    //6. 这个then 不会执行了
    console.log(111111111);  
}, err => {
    console.log(2222222);   
})

总结:
1.只有两种情况会失败:返回一个失败的promise(就是promise的执行器里掉的reject方法参数), 或者 抛出异常错误
2.每次执行promise实例.then()的时候都会返回一个新的promise,这样才可以一直调用.then(),而且是新的,不然返回的同一个promise 成功就不能失败了,失败就不能成功了
3.then()里面的onrejected 可以不写,然后写一个.catch(err =>{}) 到时候有过自己有onrejected 就走自己的onrejected,如果没有就走 .catch()

发布了57 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Eva3288/article/details/104165481