JavaScript中Promise的简单使用及其原理详解
作者:Hansel.Wn
Promise是ES6最重要的特性之一,今天来系统且细致的研究一下Promise的用法以及原理。
按照我往常的理解,Promise是一个构造函数,有all、resolve、reject、then、catch等几个方法,一般情况下,在涉及到异步操作时才会用到Promise。
所以我接下来先new一个Promise对象,并在其中进行一些异步操作:
// 使用Promise的时候一般会把它包裹在一个函数中,并在函数的最后返回这个Promise对象 function runPro()( var a = new Promise((resolve, reject) => { setTimeout(() => { console.log('work done!'); resolve('success'); }, 1000); }); return a; ) runPro()
在上面的代码中,Promise的构造函数接收一个箭头函数作为参数,这个箭头函数又有两个参数,分别是resolve和reject,我在这个箭头函数中使用setTimeout进行了一些异步操作,异步操作中执行了resolve方法,并给resolve方法传了一个字符串‘success’作为参数。
执行这段代码会发现,等待了1秒钟后(因为我在setTimeout中设置的等待时间是1000毫秒),输出了‘work done!’。
这时候并没有发现Promise有什么特别的作用,而且resolve和reject这两个的作用也并没有体现出来。
之前我们说过Promise这个构造函数上有then、catch方法,在上面的代码片段中,runPro函数最后return了一个Promise对象,所以我们可以在runPro函数执行完成之后使用then对Promise对象进行进一步的操作:
runPro().then((res) => { console.log('then:', res); //TODO something });
输出结果:
在runPro返回的Promise对象上直接调用then方法,then方法接收一个函数作为参数A,并且这个箭头函数也会接收一个参数B,这个参数B的值就是前面代码中resolve方法所传递的字符串‘success’。
执行代码,会在1秒后首先输出‘work done!’,紧接着输出‘then: success’。
这个时候,就可以简单的体现出来Promise的作用了,在前面的代码中,then方法就像是Promise的回调函数,当Promise中的异步操作执行完之后,通过链式调用的方式执行回调函数。
这里的关键点就在于链式调用上,当实际使用中遇见多层回调的情况时,Promise的强大之处才能够体现出来:
function runPro2(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro2'); }, 1000); }); return a; }; function runPro3(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro3'); }, 1000); }); return a; }; runPro().then(() => { return runPro2(); }).then(() => { return runPro3(); }).then(() => { console.log('all done') })
到这里为止,大概已经明白了,Promise是一个在异步操作过程中,等待其中异步操作完成之后执行其回调函数的一种结构体。但是其中的原理还是模糊不清,其中resolve和reject这两个参数还没有搞清楚,只知道在前面的几个代码片段中都调用了resolve函数,resolve是做什么的并没有体现出来。
关于resolve和reject,我以前的理解是Promise中的异步操作执行成功后调用resolve函数,异步操作执行失败后调用reject函数,后来发现这种理解其实是不准确的。
在理解这两个函数的正确作用之前,我们首先要知道Promise一个重要的特性:状态
Promise的状态:
一个Promise对象的当前状态必须为以下三种状态中的一种:等待(Pending)、完成(Fulfilled)、拒绝(Rejected)。
Pending:
异步操作完成之前,Promise处于等待状态,这时候的Promise可以迁移至Fulfilled或者Rejected。
Fulfilled:
异步操作完成之后,Promise可能从Pending状态迁移至Fulfilled状态,Fulfilled状态的Promise必须拥有一个不可变的终值,并且Fulfilled状态的Promise不能迁移为其他状态。
Rejected:
异步操作完成之后,Promise可能从Pending状态迁移至Rejected状态,Rejected状态的Promise必须拥有一个不可变的拒绝原因,并且Rejected状态的Promise不能迁移为其他状态。
了解了Promise的三种状态之后,我们再来说说resolve和reject这两个函数的作用:
- resolve函数将Promise设置为Fulfilled状态,reject函数将Promise设置为Rejected状态。
- 设置为Fulfilled或者rejected状态后,即表示Promise中的异步操作执行完成,这时程序就会执行then回调函数。
- resolve和reject函数传递的参数,将由then函数中的箭头函数接收。
实际上,理解Promise的关键点就在于这个状态,通过维护状态、传递状态的方法来进行及时的回调。
所以,如下面代码所示,当使用Promise进行异步操作的时候,其中有几个关键点需要特别注意:
在一个函数中new了一个Promise对象之后,函数的最后必须把这个Promise对象return出来,否则这个函数就无法使用then函数进行回调;
异步操作中必须执行resolve或者reject函数,否则这个Promise一直处于Pending状态,代码就不会执行它的回调函数。
function runPro(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('resolve'); console.log('this is runPro'); }, 1000); }); return a; } runPro().then((res) => { console.log(res); })
同样的道理,当异步操作执行失败时,代码通过执行reject函数的方式,将Promise的状态设置为Rejected,并返回一个拒绝原因:
function runPro(item){ var a = new Promise((resolve, reject) => { setTimeout(() => { if(item >= 18) { console.log('item 大于 18'); resolve('一切正常!'); }else { console.log('item 小于 18'); reject('18+电影不允许放映!'); } }, 1000); }); return a; } runPro(13) .then((res) => { console.log('resolve:',res); },(rej) => { console.log('reject:', rej); })
我们给runPro函数传递不同的参数,runPro接受参数后进行一个异步的判断,如果这个参数的值小于18,执行reject函数,反之则执行resolve函数,异步操作完成之后,执行then回调函数,这里的回调函数可以接收两个箭头函数作为参数,分别对应了resolve函数的回调和reject函数的回调,这两个箭头函数可以分别拿到resolve和reject传递的参数。
如下面截图所示,分别给runPro传递两个不同值后,得到了两种不同的结果:
catch函数的用法
在Promise中,catch函数可以替代reject函数使用,用来指定接收reject的回调:
function runPro(item){ var a = new Promise((resolve, reject) => { setTimeout(() => { if(item >= 18) { console.log('item 大于 18'); resolve('一切正常!'); }else { console.log('item 小于 18'); reject('18+电影不允许放映!'); } }, 1000); }); return a; } runPro(13) .then((res) => { console.log('resolve:',res); }) .catch((rej) => { console.log('catch:', rej); })
如上面代码所示,对调函数then只有一个箭头函数作为参数,这种情况下,这个箭头函数就被指定用来接收resolve函数的回调,而reject函数的回调则被catch函数来接收:
这个地方使用catch函数来接收reject的回调有一个优点,当前面的then回调函数中出现位置错误时,catch函数可以对错误信息进行处理,而不会导致代码报错。这个原理和常用的try/catch语句相同。
function runPro(item){ var a = new Promise((resolve, reject) => { setTimeout(() => { if(item >= 18) { console.log('item 大于 18'); resolve('一切正常!'); }else { console.log('item 小于 18'); reject('18+电影不允许放映!'); } }, 1000); }); return a; } runPro(19) .then((res) => { console.log(adc); // 这里的adc是一个未定义的变量,当代码执行到这里时,会抛出Error信息导致代码卡死 console.log('resolve:',res); }, (rej) => { console.log('reject:',rej); }); runPro(19) .then((res) => { console.log(abc); // 这里的abc是一个未定义的变量,但是由于后边使用.catch函数进行了异常捕获,所以程序不会报错。而且错误原因也会作为参数传递到后面.catch函数的参数中 console.log('resolve:',res); }) .catch((rej) => { console.log('catch:', rej); })
all函数 / race函数并行异步操作
Promise的all函数和race函数都提供了并行异步操作的能力,二者的区别在于,当这些并行的异步操作耗时不同时,all函数是在所有的异步操作都执行完之后才会执行,而race函数则会在第一个异步操作完成之后立即执行。
function runPro1(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro1'); }, 1000); }); return a; } function runPro2(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro2'); }, 2000); }); return a; }; function runPro3(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro3'); }, 3000); }); return a; }; Promise.all([runPro1(), runPro2(), runPro3()]) .then((res) => { console.log('all:', res); })
function runPro1(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro1'); }, 1000); }); return a; } function runPro2(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro2'); }, 2000); }); return a; }; function runPro3(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro3'); }, 3000); }); return a; }; Promise.races([runPro1(), runPro2(), runPro3()]) .then((res) => { console.log('all:', res); })
如上两个代码片段所示,all函数和race都接收一个数组作为参数,这个数组中的值就是我们要进行并行执行的异步操作。这里我们同样使用then函数作为异步操作完成的回调函数。
同时我们通过console输出发现,在race函数的回调函数开始执行的时候,另外两个没有执行完成的异步操作并没有停止,依旧在执行。
到此这篇关于JavaScript中Promise的简单使用及其原理详解的文章就介绍到这了,更多相关JavaScript Promise内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!