JS-史上最小清新的Promise

463 阅读4分钟

回调地狱

就是说,在异步js里,当一个异步任务的执行需要依赖另一个异步任务的结果时,一般会将两个异步任务嵌套起来,如果嵌套的太多了,回调套回调,导致很难凭直觉看懂代码

(所以这并不是一个多高深的词,不要被它的名字唬住了。)

什么是Promise

Promise是ES6新增的语法,是异步编程的一种解决方案,解决了回调地狱的问题。其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。

(说白了就是让异步代码看起来好懂。。) 例:

let p = new Promise(function(resolve, reject){
		//做一些异步操作
		setTimeout(function(){
			console.log('Promise执行完成');
			resolve('任何数据');
		}, 2000);
	});

其执行过程是:执行了一个异步操作,也就是setTimeout,2秒后,输出“执行完成”,并且调用resolve方法。

Promise三种状态

Promise有三种状态,初始状态是pending,

可以通过函数resolve变为rejected状态,也可以通过resolve函数变为resolved状态,

状态一旦改变就不能再次变化(Promise规范规定除了pending状态,其他状态是不可以改变的)。

Promise用法

实际上Promise上的实例promise是一个对象,不是一个函数。在声明的时候,Promise传递的参数函数会立即执行,因此Promise使用的正确姿势是在其外层再包裹一层函数,在需要的时候去运行这个函数,如:


<div onClick={promiseClick}>开始异步请求</div>
 
const promiseClick =()=>{
	 console.log('点击方法被调用')
	 let p = new Promise(function(resolve, reject){
		//做一些异步操作
		setTimeout(function(){
				console.log('执行完成Promise');
				resolve('要返回的数据可以任何数据例如接口返回数据');
			}, 2000);
		});
        return p;
   }

executor(resolve,reject)

executor是带有 resolve 和 reject 两个参数的函数 。

Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回新建对象前被调用)。

resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦完成,可以调用resolve函数来将promise状态改成fulfilled,或者在发生错误时调用reject函数将它的状态改为rejected。

.then

每个Promise的实例对象,都有一个then的方法,这个方法就是用来处理之前各种异步逻辑的结果。

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用(成功时调用),接受的参数是Promise里resove()括号里的值,在下面代码中是num;第二个回调函数是Promise对象的状态变为rejected时调用(失败时调用),参数是reject()括号里的值,在下面代码中是:'数字太于10了即将执行失败回调'。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。


function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
 
	promiseClick().then(
		function(data){
			console.log('resolved成功回调');
			console.log('成功回调接受的值:',data);
		}, 
		function(reason){
			console.log('rejected失败回调');
			console.log('失败执行回调抛出失败原因:',reason);
		}
	);	

执行结果:
image.png

then链式调用

对于Promise的then()方法,then总是会返回一个Promise实例,因此你可以一直调用then,形如run().then().then().then().then().then()…

在一个then()方法调用异步处理成功的状态时,你既可以return一个确定的“值”,也可以再次返回一个Promise实例。

当返回的是一个确切的值的时候,then会将这个确切的值传入一个默认生成的Promise实例,并且这个Promise实例会立即置为fulfilled状态,以供接下来的then方法里使用。看代码:

let num = 0
    let run = function() {
        return new Promise(resolve => {
            resolve(`${num}`)})
    }

    run().then(val => {
        console.log(val)
        return val
    })
        .then(val =>{
            val++
            console.log(val)
            return val
        })
        .then(val =>{
            val++
            console.log(val)
        })
// 输出: 0 1 2

catch

与Promise对象方法then方法并行的一个方法就是catch,与try catch类似,catch就是用来捕获异常的,也就是和then方法中接受的第二参数rejected的回调是一样的(换句话说,用不用都一样,用了代码更好理解),如下:

function promiseClick(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
 
	promiseClick().then(
		function(data){
			console.log('resolved成功回调');
			console.log('成功回调接受的值:',data);
		}
	)
	.catch(function(reason, data){
		console.log('catch到rejected失败回调');
		console.log('catch失败执行回调抛出失败原因:',reason);
	});	

all用法

与then同级的另一个方法,all方法接收一个Promise数组参数。数组里的所有Promise都执行了resolve(都成功执行完了)才会调用then,并将几个Promise的resolve返回值存在一个数组中传给then做参数。这里的results就是一个结果数组。如果有一个Promise执行失败,调用了reject函数,则会控制台报错,报错内容为reject的内容。

有一个场景是很适合用all的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

function promiseClick1(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick2(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick3(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
Promise.all([promiseClick3(), promiseClick2(), promiseClick1()])
		.then(function(results){
			console.log(results);
		});

执行结果:

image.png

image.png

race用法

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调。

function promiseClick1(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('1s随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('1s数字太于10了即将执行失败回调');
				}
			}, 1000);
		   })
		   return p
	   }
	   function promiseClick2(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('2s随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('2s数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick3(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('3s随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('3s数字太于10了即将执行失败回调');
				}
			}, 3000);
		   })
		   return p
	   }
Promise
		.race([promiseClick3(), promiseClick2(), promiseClick1()])
		.then(function(results){
			console.log('成功',results);
		},function(reason){
			console.log('失败',reason);
		});

执行结果:

image.png

真实场景代码举例

// 执行搜索帮助接口,获取数据
      const getShTableData = val => {
        const { table, initialValue = {} } = searchHelper;
        return new Promise(async (resolve, reject) => {
          if (process.env.MOCK_DATA === 'true') {
            await sleep(500);
            const { data } = cloneDeep(require('@/mock/tableData').default);
            return resolve(data.items);
          } else {
            const params = merge({}, table.fetch?.params, formatParams({ ...initialValue, ...createShFilters(val) }), { currentPage: 1, pageSize: 500 });
            try {
              const res = await table.fetch.api(params);
              if (res.code === 200) {
                const list = get(res.data, table.fetch.dataKey) ?? (Array.isArray(res.data) ? res.data : []);
                return resolve(list);
              }
            } catch (err) {}
            reject();
          }
        });
      };
      
       getShTableData(val)
                      .then(list => resetSearchHelperValue(list, val))
                      .catch(() => clearSearchHelperValue(!0));

手写Promise

当面试官认为你可能是个大佬,通常会让你手写一个Promise来验证你值不值一个高工资。

var a=1;
const PENDING='pending';
    const RESOLVED='resolved';
    const REJECTED='rejected';
    function myPromise(fn){
       const that=this;
       that.state=PENDING;
       that.resolvedCallbackArray=[];
       that.rejectedCallbackArray=[];
       function resolve(value){
           that.state=RESOLVED;
           that.resolvedCallbackArray.forEach(cbFn=>cbFn(value));
                }
       function reject(value){
           that.state=REJECTED;
           that.rejectedCallbackArray.forEach(cbFn=>cbFn(value))
           console.log('reject调用',value)
                }
       try{
           fn(resolve,reject);
                }catch(e){
              reject(e);
                }

            }
    myPromise.prototype.then=function(onFulfilled,onRejected){
       const that=this;
       that.resolvedCallbackArray.push(onFulfilled);
       that.rejectedCallbackArray.push(onRejected);
       return that;}
       
function f(){
return new myPromise((resolve,reject)=>{
       if(a===1){
       setTimeout(()=>{
       resolve('成功');
                },2000)
                }else{
       reject('失败')
                }
            })
}
f().then(value=>{
       console.log('第一次',value)
            })

执行结果:

image.png