学习分享——Promise的深入浅出

356 阅读14分钟

Promise

Promise简介

Promise 是一个构造函数,可以封装一个方法,以接收其成功或者失败的值

Promise的作用

Promise支持链式调用,可以解决回调地狱的问题

一个异步函数的不断地去调用 其他异步 函数

链式调用

promise:在promise中启动异步任务 => 任务结束后返回promise对象 => 给返回的promise 对象绑定新的回调函数 (可以在绑定的异步任务结束后绑定多个异步任务)
window.onload = () => {
    console.log(11);

    document.querySelector('button').addEventListener('click', function () {
        function rand(m, n) {
            return Math.floor(Math.random() * n + m);
        };

        let randNum = rand(1, 100);
        //    Promise是一个构造函数,接收一个回调函数,这个回调函数接收两个参数,一个是 resolve ,一个是 reject 并且这两个参数也是一个函数,用来修改 promise 的状态
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                // randNum <= 30 ? alert('恭喜您中奖了') : alert('很抱歉,您没中将');
                if (randNum <= 30) {
                    resolve(
                        randNum
                    ); // resolve本身是解决的意思,可以将promise的状态修改为成功,如果向 resolve 传入参数,那么在 then 中会作为成功状态下的函数 的实参传递
                } else {
                    reject(
                        randNum); //reject 本身是拒绝的意思,可以将promise的状态修改为失败,如果向 reject 传入了参数,那么在 then 中会作为 失败 状态下的函数 的实参传递
                };
            }, 2000);
        });
        p.then(
            (num) => {
                alert('恭喜您,中奖了,中奖数字为' + num) // 可以通过 向 resolve 内传递参数到 resolve 成功状态下的函数里作为实参
            }, //then可以判断promise的状态并调用对应的参数函数,如果为成功,则调用和 promise 对象对应的参数函数,这里是 resolve 函数
            (reson) => { //可以通过 向 reject 内传递参数到 reject 失败状态下的函数中,作为实参
                alert('没中奖,您的数字为' + reson)
            } // 如果 promise 的状态为失败,则调用失败函数,这里是 reject 函数
        )
    })
}

try-catch语法

try {
    定义断言
} catch (err) {
    错误 catch 块
} finally () {

}

该语法用来判断程序运行中可能存在的错误,如果有则抛出处理,如果没有,则不会报错

try 定义为 程序测试的代码块

catch 定义为 try 代码块抛出错误接收的代码块 

finallytry-catch 之后无论是否存在错误都会执行的代码块

一般在 try 代码块内 用 throw 抛出错误,抛出的错误信息将会有 catch 块接收,且仅仅是 第一个 抛出 才会被 catch 接收到

try { // try 试图
    if (1) throw 2; // 用来抛出错误,可以配合 if-throw 抛出错误 
} catch (err) { // catch 捕捉
    console.log(err); // 用来接收 if-throw 抛出的错误信息
} finally { //finally 之后,终于
    console.log('try-catch 初体验'); // 无论是否存在错误,finally 都会执行
}

注意:如果 throw 出现错误且没有 cathc 代码块,则会抛出错误并结束当前代码,后面的代码不再执行

corssorigin

这个属性可以解决部分跨域问题

一般可以使用该属性的有 

img,audio,vedio,link,script 标签等

如果这些属性 没有该标签 在访问 跨域资源 的时候,可能会被浏览器限制行为

该属性的值有:

anymous 将请求的跨域模式改为 CORS,凭据模式为 same-origin

use-credentials 将请求的跨域模式设置为 CORS ,凭据模式为 include

PromiseState 状态信息

PromiseState 记录了 promise 对象的当前的状态,

pending 初始化状态

resolve 和 reject 只有能由 pending 改变

要么为 resolved/fullfilled 成功

要么为 rejected 失败

是实例对象中的一个属性

[PromiseState]

不可能由 resolve 转为 reject 
也不可能由 reject 转为 resolve 

注意:

1.只有 resolve 和 reject 这两种状态。且 一个 promise 只能改变一次

2.promise 对象无论是成功还是失败,总能返回一个结果

3.成功的结果数据一般都是 value ,失败的结果数据 一般都是 reason

PeomiseResult 对象或者失败的结果

PromiseResult 存储的是 Promise 对象成功或者失败的结果

PromiseResult 的值只能由

resolve 函数

reject 函数

参与并修改

Promise.PromiseResult 的值由改变其状态的方法的参数决定,不管是成功还是失败一定会调用一个方法,这个方法一定会传递一个实参,那么这个实参就是 PromiseResult 的值

Promise的工作流程

1. 实例化 promise 对象,并调用传入的回调函数

2.如果回调函数调用返回成功,则将 Pending 改变为 resolved/fullfilded 

如果回调函数调用返回失败,则将 pending 改编为 rejected

3.调用 then 方法,传入两个回调函数 

4.如果 pending 的状态是 resolved.fullfiled,那么就调用第一个方法,也就是 resolve 回调函数

如果 pending 的状态是  rejected ,则调用第二个方法,也就是 reject 回调函数

Promise 相关的 API

执行器

Promise在被实例化的时候要求传入一个回调函数,回调函数内存在两个子参数回调函数,一个是 resolve,一个是 reject,这个回调函数就是执行器,而两个子构造函数叫做状态修改器

function execute(resolve, reject) {
    console.log(11);
}; //执行器函数
let p = new Promise(execute);
//执行器函数在作为回调函数传入 Promise 的时候就会立即以同步代码执行
console.log(22);
/* 
        11
        22
        */

then回调函数

Promise.prototype.then((resolveCallBack,rejectCallBack)=>{})

then是定义在 Promise 的原型对象上的函数,这个函数在Promise 实例对象的 Pending 状态发生改变并且返回状态结果后执行,根据 pending 的状态,会执行 resolve 回调或者是 reject 回调,如果成功则执行 resolve 回调,如果失败 则执行 reject 回调

let p = new Promise((resolve, reject) => {
    reject('reason'); // 回调函数的参数会被作为 then 的实参返回,并传入then对应状态的方法中
});
p.then((value) => {
    console.log(value); // value ,如果pending 的状态为 fullfiled 则执行 resolve 方法
}, (reason) => {
    console.log(reason); // reason 如果pending 的状态为 rejectd 则执行 reject 方法
});
console.log(p);

then方法是一个异步函数

catch失败捕捉函数

Promise.prototype.catch(reason)

catch 方法是定义在 Promise 对象上的一个方法,这个方法只能处理 pending 状态为 rejected 的结果

let p = new Promise((resolve, reject) => {
    resolve('error'); //无法输出结果,原因是 catch 方法只能处理 pending 结果为 rejected 时的状态,只会在 reject 调用的时候才会被启用
});
p.catch((reason) => {
    console.log(reason); //error
});

Promise.resolve 方法

// let p1 = Promise.resolve(
// 123); // Promise.resolve 方法是 Promise 构造函数上的一个静态成员,可以快速的得到一个Promise对象,如果传入的值为非 Promise对象,那么他的状态就是 fullfiled ,如果是一个 Promise 对象,那么Promise.PromiseStatus为 Promise  对象的状态,结果为 参数 Promise 的运行结果
// console.log(p1);
// let p2 = Promise.resolve(undefined);
// console.log(
// p2); // 状态为 fulfilled ,值为 undefined ,这就说明了 哪怕是 undefined 这种值作为 resolve 方法的参数,依然会将状态改变 为 fulfiled ,并且 Promise.PromiseResult 是 参数 ,也就是 undefined

let p3 = Promise.resolve(new Promise((resolve, reject) => {
    // resolve(
    // 'value'
    // ); // 如果参数 Promise 的状态是 fulfiled ,那么Promise.resolve 返回的Promise 对象的状态也是 fulfiled ,并且值为 参数 Promise 对象的返回值

    reject(
        'reason'
    ); // 如果参数 Promise 的状态是 rejected ,那么 Promise.resolve 返回的 Promise 对象的状态也是 rejected ,并且值为 参数 Promise 对象的结果,并且会报一个错误 Uncaught (in promise) reason , 原因是 promise 对象存在了错误,但是没有 catch 代码块去接收处理他,所以只需要使用 promise.catch 即可解决
}));
p3.catch((err) => {
    console.log(err);
})
console.log(
    p3);

总结一句话: 定义在 Promise 上的静态成员方法 Promise.resolve 可以快速的得到一个不稳定的 Promise 对象,这个对象的状态由参数决定,如果参数是非失败的 Promise对象,则对应的状态就是 fulfiled ,否则 状态是 rejected ,并且 Promise.PromiseResut 的值为参数值,或者是 rejected 的实参

补充:不管 Promise.resolve 的参数对象是 成功还是失败,结果都是该 参数对象的结果
并且返回的对象就是 参数 Promise 对象 

let p1 = new Promise((resolve, reject) => {
    resolve('value');
});
let p2 = Promise.resolve(p1);
console.log(p2 === p1); // true

Promise.reject和Promise.all方法

Promise.reject 是定义在构造函数上的静态方法,这个方法用来创建一个失败的Promise 对象,无论传入什么值,都会返回一个失败的 Promise ,和上面的一样,如果传入的是一个Promise 对象,最后返回的就是 传入的那个Promise 对象


let p1 = Promise.resolve('ok');
let p2 = Promise.reject('err');
let p3 = Promise.resolve('fun');

补充: Promise.reject 方法如果参数是一个 promise 对象,那么返回的结果永远都是该对象,且状态永远都是 rejected

let result = Promise.all([p1, p2,
                          p3
                         ]); //Promise.all 方法接收一个数组参数,其中没一个数组元素都是一个Promise对象,只有每一个数组元素的状态都为 fulfiled 的时候 Promise.all 返回的对象才为 fulfiled ,返回的 Promise.PromiseResult 就是每一个 Promise 数组元素的结果,否则 返回的对象是 失败的Promise对象,返回对象的 Promise.PromiseResult 就是 失败对象的 结果
console.log(result);
console.log(p2);
console.log(result == p2);

可以理解为是 p1 && p2 && p3 的逻辑与

Promise.race 方法

Promise.race 方法和 Promise.all 方向类似,接收的参数都是一个数组,而且都会返回一个Promise对象,但是不同点是Promise.race 方法返回的对象的状态和结果是第一个状态发生改变的 Promise 对象的状态和结果,如果全是异步对象,那么第一个到达消息队列的异步任务的结果的状态和值就是 Promise.race 方法的状态和结果

   let p1 = Promise.reject('ok');
        let p2 = Promise.reject('err');
        let p3 = Promise.resolve('fun');

        let result = Promise.race([p1, p2,
            p3
        ]); //和Promise.all类似,传入的参数也是一个由Promise对象组成的数组,但是不同的是,只有第一个改变状态的Promise,才会影响 Promise.race 的返回对象,如果第一个改变的状态是 fulfiled ,那么返回的 Promise 对象的章台就是 fulfiled ,结果就是该对象的结果,如果改变的状态是 rejected ,那么返回的对象就是 rejected 状态,结果就是该Promise 对象的结果
        console.log(result);

Promise 的几个关键问题

如果改变 Promise 对象的状态

调用 resolve 回调以改变状态为 fulfiled

let p = new Promise((resolve, reject) => {
    // 1.调用resolve以改变状态为fulfiled
    // resolve('value'); //Promise {<fulfilled>: 'value'}
});
console.log(p);

调用 reject 回调以改变状态为 rejected

let p = new Promise((resolve, reject) => {

    // 2.调用 reject 以改变状态为 rejected
    // reject('reason');//Promise {<rejected>: 'reason'}

});
console.log(p);

throw 抛出错误 以改变状态为 rejected

let p = new Promise((resolve, reject) => {

    // 3.抛出错误以改变状态为 rejected
    throw 'err'; //Promise {<rejected>: 'err'}
});
console.log(p);

Promise的多个回调问题

如果给Promise对象指定了多个回调,只要Promise 对应的状态发生了改变,那么所有指定的回调都会执行

如果状态是 pending ,则不会执行任何回调函数

let p = new Promise((resolve, reject) => {
    // resolve();//如果状态没有发生改变,那么任何回调都不会执行
});
p.then(() => {
    console.log(1);
});
p.then(() => {
    console.log(2);
});
p.then(() => {
    console.log(3); //依次输出 1,2,3 输出是一个同步任务,只要状态发生了改变就一定会执行对应的回调
});

改变Promise状态和回调函数谁先谁后

分两种;

是Promise的状态先改变还是 回调函数先执行

是回调函数先执行还是 Promise的状态先改变

// 状态和回调,谁先执行
let p = new Promise((resolve, reject) => {
    //如果执行器的代码块是同步任务,那么就是先改变状态,再执行回调
    // resolve(11);
    //如果执行器的代码块是异步任务,那么就先执行回调,然后再改变状态,但是执行回调不会拿到数据,只有当 状态修改器修改Promise的状态的时候才会 拿到状态
    setTimeout(() => {
        resolve(11); //简单来说,只有状态发生了改变,才会调用 then 里面的回调函数拿到数据,但是如果是同步任务,那么先改变状态,再执行回调
    }, 1000)
});
p.then(() => {
    console.log(11);
}, () => {
    console.log(22);
})

then的返回值

let p = new Promise((resolve, reject) => {
resolve('value');
//在没有返回值的情况下,then 方法返回的Promise对象的状态根据原Promise的修改器决定.如果原Promise对象修改了状态,那么不管状态是成功还是失败,then返回的对象的状态都是 fulfiled ,但是不会有 PromiseResult,实例化的时候如果修改了状态为 rejected ,也不会报错,并且如果原Promise 没有发生状态的改变,那么then 返回的Promise的状态就是 pending

/* 
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined

*/
// reject('reason');

// 说白了,原Promise的状态只是决定then方法调用哪个回调,和返回的毛关系都没有
});
// console.log(p);
let result = p.then((value) => {
console.log(value);
// 1.如果返回的值是一个非 Promise 值,那么返回的就是 fulfiled 状态。结果就是 返回值
// return 521;

/* 
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 521
*/
//2.如果抛出错误,那么状态就是 rejected ,结果就是错误的信息
// throw 'err';

/* 
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "err"

*/

// 3.如果返回的是一个Promise对象,那么返回的对象的状态和返回结果就由该promise的状态和结果决定
return new Promise((resolve, reject) => {
// resolve('value'); //如果成功,返回的Promise的状态就是成功,结果就是返回的对象结果

/* 
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "value"
*/

reject('reason'); //如果失败,返回的Promise的状态就是失败,结果也是返回的对象的结果

/* 
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "reason"

*/
})
}, (reason) => {
console.log(reason);
});
console.log(result);

报错问题

let p = new Promise((resolve, reject) => {
    reject('reason');
});
p.then(value => {
    console.log(value)
}, reason => {
    console.log(reason)
}) //初始化状态的时候是不会报错的

// 如果使用Promise.reject()方法就会 报错
let p2 = Promise.reject('err');
console.log(p2);
//如果抛出错误一定会报错
let p3 = new Promise((resolve, reject) => {
    throw 'err'; //哪怕这Promise的对象的状态是 fulfiled 成功状态,只要抛出错误,就一定会被强制转成 rejected ,并且结果就是错误信息
    resolve('value');
});
console.log(p3);

Promise串联多个操作任务

因为 then 也可以返回一个新的Promise对象,那么使用链式调用思想,可以一直串联调用任务

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('我是原Promise'); //先打印这个
        resolve('value');
    }, 1000)
});
let p2 = p.then(value => {
    setTimeout(() => {
        console.log('我是第一个then的返回值'); //最后打印这个
        return 123; //因为 then 方法也会返回一个新的Promise对象,那么可以根据原Promise对象发起异步调用链
    }, 1000)
});
let p3 = p2.then((value) => {
    console.log('我是第二个then的返回值'); //第二个打印这个
});

/* 
        我是原Promise
        Promise的任务串联.html:23 我是第二个then的返回值
        Promise的任务串联.html:18 我是第一个then的返回值
        */

//造成上述问题的原因其实很简单,因为then 方法返回的结果是一个promise,那么不管 then 里面回调函数是一个同步还是异步任务,他都会有一个返回值,那么then 继续 then ,如果第一个then的是一个同步方法,那么一定会先打印 我是第一个then的返回值 ,再打印 我是第二个then的返回值,和 teturn ,return只是决定了什么时候才能得到返回的对象,如果是一个异步任务,那么一定先打印 我是第二个then的返回值,因为 then 调用的回调是一个同步任务,不会收到 return 的影响

// 输出三个Promise
console.log(p);
console.log(
    p2
); //注意,then 方法是一个同步任务,和什么时候返回 return 无关,如果 return 嵌套在了异步任务里面,一定会先输出 Prpmise,然后再 return ,也就说,p2 的状态是成功,但是结果是 undefined 
console.log(p3);


/* 
        p:
        Promise {<pending>}
        [[Prototype]]: Promise
        [[PromiseState]]: "fulfilled"
        [[PromiseResult]]: "value"


        p2:
        Promise {<pending>}
        [[Prototype]]: Promise
        [[PromiseState]]: "fulfilled"
        [[PromiseResult]]: undefined



        p3:
        Promise {<pending>}
        [[Prototype]]: Promise
        [[PromiseState]]: "fulfilled"
        [[PromiseResult]]: undefined
        */

Promise的异常穿透

let p = new Promise((resolve, reject) => {
    // reject('ok'); //这里输出了错误 Promise的异常穿透.html:21 ok 并指定了行号
    resolve('ok');
});
let p2 = p.then(() => {
    console.log(11);
}).then(() => {
    console.log(22);
    throw 'err' //如果在链式调用中抛出了错误,那么就不再执行后面的回调,回直接在最后面的 catch 方法中处理错误
}).then(() => {
    console.log(33);
}).catch(reason => {
    console.warn(reason);
    // return '处理了'
    // throw 'err'
    return new Promise((resolve, reject) => {
        // resolve('ok');
        reject('reason');
    })
}); //如果中间出现了错误,可以在链式调用的尾端添加 catch 方法捕捉错误
console.log(p2);

简而言之:

异常穿透是一种错误处理机制,在链式调用的终点添加 catch 方法或者 then 的错误回调 以捕捉错误,链式调用途中出现任何错误,回直接进入 catch 方法处理,而不会再执行后面的回调函数,错误之前的代码依旧会被正常执行

原因是:

上一个 then 返回的 promise 对象没有处理 错误 ,那么整个的 promise 链都是 失败的 ,而且错误信息会被保留,直到 最后的 错误处理,那么输出的结果 肯定就是 错误信息,和 失败的Promise
注意,如果处理了错误,那么最后返回的Promise的状态就是成功,如果有 return 值,遵循上面的三种状态改变

即:如果返回非 promise 数据,那么就是 fulfiled 
	如果返回的是 promise 的数据,那么状态和结果由返回的promise 的状态和结果决定
    如果在 catch 内抛出错误,那么返回的 Promise 的状态和结果就是 失败

Promise链的中断

let p = new Promise((resolve, reject) => {
    resolve('value');
});
p.then(() => {
    console.log(11);
})
    .then(() => {
    console.log(22);
    //如需中断 promise 链,方法一:返回一个未改变状态的promise 
    // return new Promise((resolve, reject) => {}); //11,22
    // 方法二:抛出错误
    // throw 'err'; //11,22
})
    .then(() => {
    console.log(33);
});