Promise的理解和使用

Promise是什么?

理解

1. 抽象表达
  1. Promise是一门新的技术(ES6规范)

  2. Promise是JS中进行异步编程的新解决方案

    备注:旧的方案是单纯使用回调函数

2. 具体表达
  1. 从语法上来说:Promise是一个构造函数
  2. 从功能上来说:Promise对象用来封装一个异步操作并可以获取其成功或失败的结果值。

Promise对象的状态改变

Promise的状态指的是实例对象中的一个属性PromiseState,它的值有三个:

  1. pending:未决定的
  2. resolve/fulfilled:成功
  3. rejected:失败

Promise的状态改变只有下面的三种情况:

  1. pending变为resolved
  2. pending变为rejected
  3. 抛出异常:如果当前为pending就会变为rejected

说明:只有这3种,且一个Promise对象只能改变一次,无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般称为value,失败的结果数据一般称为reason

Promise对象结果值属性

Promise实例对象中还有一个属性PromiseResult,保存着对象成功/失败的结果。要想改变它的值,只有通过给resolve()或者reject()方法传参,成功时,将对应数据传入resolve()方法,失败时,将对应的数据传入reject()方法,之后可以在then(value => {}, reason => {})方法中取出,value对应着resolve()接收的参数,reason对应着reject()接收的参数。

Promise的基本使用

需求:点击按钮,1秒后显示是否中奖(30%概率中奖)。若中奖,弹出:恭喜恭喜,奖品为10万RMB玛莎拉蒂优惠券;若未中奖弹出:再接再厉。

html页面,放个标题和按钮即可:

<h2>Promise 初体验</h2>
<button>点击抽奖</button>

不使用Promise的js代码:

<script>
    // 生成随机数
    function rand(m, n) {
    return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
/** 需求:
 * 点击按钮, 2s 后显示是否中奖(30%概率中奖)
 * 若中奖,弹出:恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券
 * 若未中奖弹出: 再接再厉 
 */

// 旧的方法
// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
    // 定时器
    setTimeout(() => {
        // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了(简易的算法)
        // 获取从 1 - 100 的一个随机数
        let n = rand(1, 100);
        // 判断
        if (n <= 30) {
            alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券');
        } else {
            alert('再接再厉');
        }
    }, 1000);
})
</script>

使用Promise,js代码:

<script>
    // 生成随机数
    function rand(m, n) {
    return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}

// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
    // Promise 形式实现
    // resovle 解决  是函数类型的数据 异步任务成功时调用
    // reject  拒绝  是函数类型的数据 异步任务失败时调用
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了
            // 获取从 1 - 100 的一个随机数
            let n = rand(1, 100);
            // 判断
            if (n <= 30) {
                resolve(); // 将 promise 对象的状态设置为 成功
            } else {
                reject();  // 将 promise 对象的状态设置为 失败
            }
        }, 1000);
    });

    // 调用 then 方法
    // then方法的第一个回调函数,promise对象状态为成功时调用,第二个回调函数,promise对象状态为失败时调用
    p.then(() => {
        alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券');
    }, () => {
        alert('再接再厉');
    })
})
</script>

现在需求变更,在弹出提示时,同时告知本次抽到的数字。

不使用Promise的js代码:

<script>
    // 生成随机数
    function rand(m, n) {
    return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}

// 旧的方法
// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
    // 定时器
    setTimeout(() => {
        // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了
        // 获取从 1 - 100 的一个随机数
        let n = rand(1, 100);
        // 判断
        if (n <= 30) {
            alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券,您的中奖数字为:' + n);
        } else {
            alert('再接再厉,您抽中的数字为:' + n);
        }
    }, 1000);
})
</script>

使用Promise的js代码:

<script>
    // 生成随机数
    function rand(m, n) {
    return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}

// 获取元素对象
const btn = document.querySelector('button');
// 绑定单击事件
btn.addEventListener('click', function () {
    
    // 如果现在需要在弹出提示时,同时告知本次的号码,该怎么办?
    // 只需要将成功或失败的值分别传入 resolve() 和 reject() 方法,然后再then()方法的两个回调函数中接收即可。
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            // 30% 中奖概率,如果数字为1—100,那么我们随机数小于30,就认为它中奖了
            // 获取从 1 - 100 的一个随机数
            let n = rand(1, 100);
            // 判断
            if (n <= 30) {
                resolve(n); // 将 promise 对象的状态设置为 成功
            } else {
                reject(n);  // 将 promise 对象的状态设置为 失败
            }
        }, 1000);
    });

    // 调用 then 方法
    // then方法的第一个回调函数,promise对象状态为成功时调用,第二个回调函数,promise对象状态为失败时调用
    // value 值
    // reason 理由
    p.then((value) => {
        alert('恭喜恭喜,奖品为 10万 RMB 玛莎拉蒂优惠券,您的中奖数字为:' + value);
    }, (reason) => {
        alert('再接再厉, 您的号码为:' + reason);
    })

})
</script>

Promise的基本流程

Promise的基本流程

为什么要使用Promise?

1. 指定回调函数的方式更加灵活

  1. 旧的:必须在启动异步任务前指定
  2. promise:启动异步任务=> 返回promise对象=>给promise对象绑定回调函数(甚至可以在异步任务结束后指定一个或多个)

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

2.1 什么是回调地狱?

回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件

例如,下面的代码是Node.jsfs模块读取三个文件,并将读取到的结果进行拼接并且输出到控制台中。

fs.readFile('./resource/1.txt', (err, data1) => {
    if (err) throw err;
    fs.readFile('./resource/content.txt', (err, data2) => {
        if (err) throw err;
        fs.readFile('./resource/2.txt', (err, data3) => {
            if (err) throw err;
            console.log(data1 + data2 + data3);
        })
    })
})

这个代码片段,fs.readFile()在嵌套调用,这里就形成了回调地狱。

2.2 回调地狱的缺点
  1. 不便于阅读
  2. 不便于异常处理
2.3 解决方案?

使用promise链式调用,

终极解决方案:async/await

如何使用Promise?

API

Promise构造函数

Promise的构造函数:Promise(executor){}

  1. executor函数:执行器(resolve, reject) => {}
  2. resolve函数:内部定义成功时我们调用的函数
  3. reject函数:内部定义失败时我们调用的函数

说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行。

Promise.prototype.then方法

Promise.prototype.then方法:(onResolved, onRejected) => {}

  1. onResolved函数:成功的回调函数,通常这么写: value => {}
  2. onRejected函数:失败的回调函数,通常这么写reason => {}

说明:指定用于得到成功value的成功回调和用于得到reason的失败回调,返回一个新的Promise对象。

Promise.prototype.catch方法

Promise.prototype.catch方法:(onRejected) => {}

onRejected函数:失败的回调函数,通常这么写reason => {}

catch()只能指定失败的回调,不能指定成功的回调。其实也是使用then()实现的.

说明:catch()then()的语法糖, 相当于: then(undefined, onRejected)

Promise.resolve方法

Promise.resolve方法:(value) => {}

value:成功的数据或Promise对象

说明:返回一个成功/失败Promise对象

catch()方法和resolve()方法的使用:

<script>
    // 如果传入的参数为 非Promise类型的对象,返回的结果为 成功的Promise对象
    // 如果传入的参数为 Promise对象,则参数的结果决定了resolve的结果
    let p1 = Promise.resolve(555);
console.log(p1);    // Promise {<fulfilled>: 555}

let p2 = Promise.resolve(new Promise((resolve, reject) => {
    resolve('OK');
}));

console.log(p2);    // Promise {<fulfilled>: "OK"}

// 传入失败的Promise
let p3 = Promise.resolve(new Promise((resolve, reject) => {
    reject('Error');
}));
console.log(p3);    // Promise {<rejected>: "Error"}
// 失败的同时还报错了,原因是没有对错误进行处理,这里使用catch()进行处理就能解决报错
p3.catch(reason => {
    console.log(reason);    // Error 
})
</script>

Promise.reject方法

Promise.reject方法:(reason) => {}

reason:失败的原因

说明:返回一个失败的Promise对象

reject()方法的示例:

<script>
    // 总结,Promise.reject()方法,无论你传入什么得到的都是失败的Promise对象,并且失败的结果就是你传入的参数。
    let p = Promise.reject(555);
console.log(p);
let p2 = Promise.reject('hello');
console.log(p2);
let p3 = Promise.reject(new Promise((resolve, reject) => {
    resolve('OK');
}))    
console.log(p3);
</script>

Promise.all方法

Promise.all方法:(promises) => {}

promises参数:包含nPromise对象的数组

说明:返回一个新的Promise对象,只有所有的Promise都成功才成功,只要有一个失败了就直接失败,失败的结果就是失败的Promise对象的结果。

all()方法的示例:

<script>
    let p1 = new Promise((resolve, reject) => {
            resolve('OK');
        });
        
    let p2 = Promise.resolve('Success');
    let p3 = Promise.resolve('Oh Yeah');

    let result1 = Promise.all([p1, p2, p3]);

    console.log(result1);    // 返回一个成功的Promise对象,对象的结果是一个数组,数组中存有三个成功的promise对象的结果

    // 如果数组中 p2 是一个失败的Promise,那么all()方法返回的是一个失败的Promise对象,并且失败的结果是数组中失败的Promise对象的结果。
    p2 = Promise.reject('Error');
    let result2 = Promise.all([p1, p2, p3]);
    console.log(result2);
</script>

Promise.allSettled方法

Promise.allSettled方法:(promises) => {}

promises参数:包含nPromise对象的数组

说明:该方法返回的结果总是状态为成功的Promise对象,这个Promise对象的结果值是包含promises参数中每一个promise对象的状态以及结果值的对象数组。

注意和all()方法的区别。

allSettled()方法示例:

<script>
    // 声明两个Promise对象
    let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve('商品数据 - 1');
    // reject('ERROR')
    }, 1000);
    })    
    let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    // resolve('商品数据 - 2');
    reject('出错了');
    }, 1000);
    })    

    // 调用allSettled() 方法  该方法返回的结果总是成功的Promise对象,
    // 它的结果值是包含每一个promise对象的状态以及结果值的对象。
    let result = Promise.allSettled([p1, p2]);
    console.log(result);

    let res = Promise.all([p1, p2]);    // 全部成功,才会返回成功的Promise对象
    console.log(res);

</script>

Promise.race方法

Promise.race方法:(promises) => {}

promises参数:包含nPromise对象的数组

说明:返回一个新的Promise对象,第一个完成的Promise对象的结果状态就是最终的结果状态

race()方法的示例:

<script>
    // 同步代码
    let p1 = new Promise((resolve, reject) => {
        resolve('OK');
    })    
    let p2 = Promise.resolve('Success');
    let p3 = Promise.resolve('Oh Yeah');
    // 调用
    let result1 = Promise.race([p1, p2, p3]);
    // 代码从上往下执行,所以p1最先执行,所以result的状态和结果应该是p1的状态和结果。
    console.log(result1);    

    // 异步代码
    let p4 = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('OK');
        }, 2000);
    })
    let p5 = Promise.reject('Error');
    let p6 = Promise.resolve('Oh Yeah');
    let result2 = Promise.race([p4, p5, p6]);
    // p4为异步代码,p5 会先于p4、p6执行,所以p5的状态和结果即为result2的状态和结果。
    console.log(result2);
</script>

Promise的几个关键问题

如何改变Promise的状态?

改变Promisede状态有三种方法:

  1. resolve(value):如果当前是pending就会改变为resolved(fulfilled)
  2. reject(reason):如果当前是pending就会变为rejected
  3. 抛出异常:如果当前是pending就会变为rejected

示例:

<script>
    let p = new Promise((resolve, reject) => {
        // 1. resolve函数
        // resolve('OK');   // pending  => fulfilled(resolved)
        // 2. reject 函数
        // reject('Error');    // pending  => rejected
        // 3. 抛出错误
        // throw '出问题了';    // pending  => rejected
    })    
    console.log(p); 
</script>

一个Promise指定多个成功/失败的回调函数,都会调用吗?

指定回调用的就是then(),所以这个题目的意思是如果使用then()方法为一个promise对象指定多个回调,那么这些回调是否都会执行?

答案是:当Promise改变为对应状态时都会调用。

示例:

<script>
    let p = new Promise((resolve, reject) => {
        resolve('OK');
    })   

    // 指定回调 - 1
    p.then(value => {
        console.log(value);
    })

    // 指定回调 - 2
    p.then(value => {
        alert(value);
    })
    // 有弹框,控制台有输出,说明这两个回调都执行了。
</script>

改变Promise状态和指定回调函数谁先执行谁后执行?

  1. 都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
  2. 如何先改状态再指定回调?
    • 在执行器中直接调用resolve()/reject()
    • then()方法延迟更长时间才被调用
  3. 什么时候才能得到数据?
    • 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
    • 如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据

示例:

<script>
    let p = new Promise((resolve, reject) => {
        // setTimeout(() => {   //  异步任务
        resolve('OK');  // 同步任务
        // }, 1000)

    })    

    p.then(value => {
        console.log(value); 
    }, reason => {

    })
    // 1. 当Promise构造函数中的executor执行器函数内部是同步任务时,先改变状态,在执行then()方法指定回调(注意是指定回调,不是执行回调)

    // 2. 当Promise构造函数中的executor执行器函数内部是异步任务时,那么是先执行then()方法指定回调函数,再改变状态。
</script>

promise.then()返回新promise的结果状态由什么决定?

  1. 简单表达:由then()指定的回调函数执行的结果决定
  2. 详细表达:
    • 如果抛出异常, 新promise变为rejectedreason为抛出的异常
    • 如果返回的是非promise的任意值,新promise变为resolvedvalue为返回的值
    • 如果返回的是另一个新promise,此promise的结果就会成为新promise的结果

示例:

<script>
    let p = new Promise((resolve, reject) => {
        resolve('OK'); 
        // reject('error')
    })  

    // 执行 then 方法
    let result = p.then(value => {  // p 为成功的回调
        // console.log(value); // result的状态为fulfilled,结果为undefined,因为没有返回值
        
        // 1. 抛出错误
        // throw '出了问题';   // result 的状态为rejected,结果为'出了问题'

        // 2. 返回结果为非 Promise 类型的对象
        // return 555; // result的状态为fulfilled,结果为555

        // 3. 返回结果是Promise对象
        return new Promise((resolve, reject) => {
            // resolve('success'); // result的状态为fulfilled,结果为success

            // reject('error');  // result 的状态为rejected,结果为error

            throw '出了问题';   // result 的状态为rejected,结果为'出了问题
        })
    }, reason => {  // p 为失败的回调
        // console.log(reason);    // result的状态为fulfilled,结果为undefined,因为没有返回值。 这里本身会输出error
        
        // 1. 抛出错误
        // throw '出了问题';   // result 的状态为rejected,结果为'出了问题'

        // 2. 返回结果为非 Promise 类型的对象
        // return 555; // result的状态为fulfilled,结果为555

        // 3. 返回结果是Promise对象
        return new Promise((resolve, reject) => {
            // resolve('success'); // result的状态为fulfilled,结果为success

            // reject('error');  // result 的状态为rejected,结果为error

            // throw '出了问题';   // result 的状态为rejected,结果为'出了问题'
        })

    })

    console.log(result);
</script>

promise如何串连多个操作任务?

  1. promisethen()返回一个新的promise,可以继续调用then()方法,形成链式调用
  2. 通过then的链式调用串连多个同步或异步任务

示例:

<script>
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('OK');
        }, 1000);
    })
    p.then(value => {
        return new Promise((resolve, reject) => {
            resolve('success');
        })
    }).then(value => {
        console.log(value)  // success
    }).then(value => {
        console.log(value)  // undefined
    })    
</script>

promise异常穿透?

  1. 当使用promisethen链式调用时,可以在最后指定失败的回调
  2. 前面任何操作除了异常,都会传到最后失败的回调中处理
<script>
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('OK')
            // reject('Err')
        }, 1000);
    })
    p.then(value => {
        // console.log(111);
        throw '出问题了';
    }).then(value => {  // 也可以在这里处理上面出现的错误
        console.log(222)  
    }).then(value => {
        console.log(333)  
    }).catch(reason => {    // 这里使用catch,也可以使用then
        console.warn(reason);
    })        

    // 在调用链的最后指定失败的回调,就可以处理任务当中出现的错误,这个现象称为异常穿透。
</script>

如何中断promise链?

当使用promisethen链式调用时,想要在中间中断,不再调用后面的回调函数,有且只有一个办法:在回调函数中返回一个pending状态的promise对象。

<script>

    // 如果想要中断这个then调用链,有且只有一个方式,在需要中断的回调函数中,返回一个pending状态的promise对象
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('OK');
        }, 1000);
    })
    p.then(value => {
        console.log(111);
        // 假如后面不想让它调用了,有且仅有一个方式:返回一个pending状态的promise对象
        return new Promise(() => {});
    }).then(value => {  
        console.log(222)  
    }).then(value => {
        console.log(333)  
    }).catch(reason => {    
        console.warn(reason);
    })      
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容