解决回调地狱问题(代码臃肿、可读性差、耦合度太高、复用性差)前端
Promise是异步编程的一种解决方案,promise异步回调,能够避免层层嵌套回调。编程
Promise对象是一个构造函数,用来生成Promise实例。Promise的构造函数接收一个参数,是函数,而且传入两个参数:resolve,reject,分别表示异步操做执行成功后的回调函数和异步操做执行失败后的回调函数。数组
Promise有三种状态:pending(执行状态)、fulfilled(成功时的状态)、rejected(失败时的状态)。当promise状态发生改变,就会触发then()里面相应的回调函数处理后续步骤,当promise状态已经改变,则不会更改。promise
var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); });
运行代码,会在2秒后输出“执行完成”。注意!我只是new了一个对象,并无调用它,咱们传进去的函数就已经执行了,这是须要注意的一个细节。因此咱们用Promise的时候通常是包在一个函数中,在须要的时候去运行这个函数,如:浏览器
function runAsync(){ var p = new Promise(function(resolve, reject){ //作一些异步操做 setTimeout(function(){ console.log('执行完成'); resolve('随便什么数据'); }, 2000); }); return p; } runAsync().then(function(data){ console.log(data); //后面能够用传过来的数据作些其余操做 //...... });
咱们包装好的函数最后,会return出Promise对象,也就是说,执行这个函数咱们获得了一个Promise对象。在runAsync()的返回上直接调用then方法,then接收一个参数,是函数,而且会拿到咱们在runAsync中调用resolve时传的的参数。运行这段代码,会在2秒后输出“执行完成”,紧接着输出“随便什么数据”。dom
(1)Promise.then():用于注册状态变为fulfilled或者reject时的回调函数,接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。then()方法返回的是一个新的Promise实例,所以能够采用链式写法。异步
const promise = new Promise((resolve, reject) => { resolve('fulfilled'); // 状态由 pending => fulfilled }); promise.then(result => { // onFulfilled console.log(result); // 'fulfilled' }, reason => { // onRejected 不会被调用 })
(2)Promise.catch():在链式写法中能够捕获then中发送的异常。async
通常来讲,不要在then()里面定义rejected状态的回调函数(即then的第二个参数),而应该使用catch()方法。理由是catch()方法能够捕获前面then()方法执行中的错误,也更接近同步的写法(try/catch)。 异步编程
和传统的try/catch代码块不一样的是,若是没有使用catch()方法指定错误处理的回调函数,Promise对象抛出的错误不会被传递到代码外层,即不会有任何反应。函数
在someAsyncThing函数中产生Promise对象,内部有语法错误,浏览器运行到这一步会打印错误,可是并不会退出进程、终止脚本执行,2s后仍是输出123。也就是说Promise内部的错误并不会影响到Promise外部的代码,即“Promise会吃掉错误”。
(3)Promise.all():能够将多个Promise实例包装成一个新的Promise实例。多个Pomise任务同时执行,若是所有执行成功,则以数组的方式返回全部Promise任务的执行结果。若是有一个Promise任务rejected,则只返回第一个rejected任务的结果。
Promise.all([p1,p2,p3])接受一个数组做为参数,数组中的p1,p2,p3都是promise实例/。
let p1=new Promise((resolve,reject)=>{ resolve('p1成功'); }) let p2=new Promise((resolve,reject)=>{ resolve('p2成功'); }) let p3=Promise.reject('p3失败'); Promise.all([p1,p2]).then((res)=>{ console.log(res);// ['p1成功','p2成功'] }).catch((err)=>{ console.log(err); }) Promise.all([p1,p2,p3]).then((res)=>{ console.log(res);//p3失败 }).catch((err)=>{ console.log(err); })
注意:
Promise.all()得到的成功结果的数组里面的数据顺序与Promise.all()接收参数的顺序是一致的,即P1的结果在前,即便P1的结果获取的比P2晚。
这带了一个很大的好处:在前端开发请求数据的过程当中,偶尔会遇到发送多个异步请求要求按照发送的顺序获取数据,使用Promise.all()能够解决这个问题。
(4)Promise.race():race赛跑的意思,多个Prromise任务同时执行,返回最早执行结束的Promise任务的结果,无论这个Promise结果是成功仍是失败。
注意:Promise.race在第一个Promise对象变为fulfilled以后,并不会取消其余promise对象的执行。只是只有先完成的Promise才会被Promice.race后面的then()处理,其余promise仍是在执行的,只不过不会进入到promise.race后面的then里。
let p1=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('p1成功'); },2000) }) let p2=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('p2成功'); }) },500) Promise.race([p1,p2]).then((res)=>{ console.log(res);//p2成功 }).catch((err)=>{ console.log(err); })
async关键字
async function asyncFn() { return 'hello world'; } asyncFn();
返回结果:
关于async/await的使用规则:
(1)async函数的返回类型为Promise对象。
当返回的是promise对象时,正好符合async函数的返回类型;
当返回的是值类型时,至关于Promise.resolve(data);仍是一个Promise对象,可是在调用async函数的地方经过简单的=是拿不到这个data返回值的,由于返回值是一个Promise对象,须要用.then(data={console.log(data)})函数才能拿到。
若是没有返回值,至关于返回了Promise.resolve(undefined);
(2)非阻塞
async函数里面若是有异步过程会等待,可是async函数自己会立刻返回,不会阻塞当前线程,能够理解为,async函数工做在主线程,同步执行,不会阻塞页面渲染。async内部由await关键字修饰的异步过程,工做在相应的协程上,会阻塞等待异步任务的完成再返回。
(3)无等待
async代表程序中可能有异步过程,async的异步体如今await上。在没有await的状况下执行async函数,他会当即执行,返回一个Promise对象,而且不会阻塞后面的语句。
(4)await必须放在async函数内部使用,不能单独使用;
(5)await关键字后面跟Promise对象,async函数必须等到内部的全部await命令的Promise对象执行完,才会发生状态改变。
await后面的promise对象没必要写then,由于await的做用之一就是获取后面promise对象成功状态传递出来的参数。
执行过程:
await是让出线程的标志,当执行到await时,await后面的函数会先执行一遍,而后再跳出整个async函数来执行后面的JS栈代码。等本轮时间循环执行完了以后又跳回到async函数中等待await后面表达式的返回值,若是返回值为非Promise则继续执行async后面的代码,不然将返回的Promise放入Promise队列。
async/await的错误处理方法
await能够直接获取后面promise对象成功状态传递的参数,可是却捕捉不到失败的状态。在这里,经过给async函数添加then()/catch()方法来解决,由于async函数自己就会返回一个promise对象。
一个包含错误处理的完整例子:
function fun2(){ return new Promise((resolve,reject)=>{ var num=Math.random(); if(num<=0.5){ resolve('success'); } else{ reject('failed'); } }) } async function fun1(){ console.log('async函数'); let res=await fun2(); //注意:在async函数里,必需要将await的结果return出去,不然的话执行then()返回值都为undefined return res; } fun1().then(data=>{ console.log(data); }).catch(error=>{ console.log(error) })
(1)能更好地处理then()链式调用,代码更加优雅,几乎和同步代码同样。
(2)简洁。使用Async/await明显节省很多代码,咱们不须要写.then,不须要写匿名函数处理promise的resolve值,也不须要定义多余的data变量。
(3)中间值
假如调用promise1,而后使用promise1的返回值去调用promise2,而后使用二者的结果去调用promise3。
使用promise:
function promise1(){ return new Promise((resolve,reject)=>{ resolve('p1'); }) } function promise2(val){ return new Promise((resolve,reject)=>{ resolve(val); }) } function promise3(val1,val2){ return new Promise((resolve,reject)=>{ resolve(val1+","+val2); }) } function fun(){ return promise1().then((v1)=>{ return promise2(v1).then((v2)=>{ return promise3(v1,v2); }) }) } fun().then((data)=>{ console.log(data); })
使用async/await
async function fun2(){ let v1=await promise1(); let v2=await promise2(v1); return promise3(v1,v2); } fun2().then((data)=>{ console.log(data); })
(1)错误会被吃掉
throw new Error('error'); console.log(‘last’); //因为throw error的缘故,代码被终止,因此不会输出'last'。 对于promise let promise = new Promise(() => { throw new Error('error') }); console.log(‘last’);//会正常输出‘last’
说明Promise内部的错误不会影响到Promise外部的代码,这种状况一般称为“吃掉错误”。
这并非Promise独有的局限性,try...catch也是这样,一样会捕捉一个异常并简单的吃掉错误。
正是由于错误被吃掉,因此Promise中的错误很容易被忽略掉,这也是为何通常推荐在Promise链的最后添加一个catch方法。
(2)没法取消
Promise一旦建立就会当即执行,没法中途取消。
(3)没法得知pending状态。
当处于pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。
以上基本能够实现简单的同步代码,即改变状态操做使用的是同步,可是当resolve在setTimeout内执行时,then时的state仍是pending等待状态,这就须要咱们在then调用的时候,将成功和失败存到各自的数组,一旦reject或者resolve就调用他们。
完整代码(只能实现一个then,还不能链式调用)
class Promise1{ constructor(fn){ //定义三个状态 this.state='pending';//初始状态 this.value=undefined;//成功的值 this.reason=undefined;//失败的缘由 //成功存放的数组 this.onResolvedArr=[]; //失败存放的数组 this.onRejectArr=[]; let resolve=(value)=>{ //用箭头函数改变this指向 if(this.state=='pending'){ this.state='fulfilled'; this.value=value; //一旦执行resove,调用成功数组的函数 this.onResolvedArr.forEach(fun=>fun()) } }; let reject=(reason)=>{ if(this.state=='pending'){ this.state='rejected'; this.reason=reason; this.onRejectArr.forEach(fun=>fun()) } } //自动执行函数 try{ fn(resolve,reject) }catch(e){ reject(e); } } then(onFulfilled,onRejected){ //状态为成功时执行onFulfilled方法并传值 if(this.state=='fulfilled'){ onFulfilled(this.value); } //状态为失败时执行onRejected方法 if(this.state=='rejected'){ onRejected(this.reason); } //当state的状态为pending时将要执行的方法放到数组中和,再调用resolve或reject时执行。 if(this.state=='pending'){ this.onResolvedArr.push(()=>{ onFulfilled(this.value); }) this.onRejectArr.push(()=>{ onRejected(this.reason); }) } } } var p=new Promise1(function(resolve,reject){ setTimeout(function(){ resolve('成功'); },1000) }) p.then(function(data){ console.log(data);//成功 },function(err){ console.log(err) })
Promise.then()的链式调用是经过返回一个新的Promise实现的。
既然jQuery经过返回this实现链式调用,为何promise.then()不返回this实现呢?
如:
var promise2 = promise1.then(function (value) { return Promise.reject(3) })
假如then函数执行返回this调用对象自己,那么promise2==promise1,promise2的状态也应该等于promise1的状态同为resolved。而onResolved回调函数中返回的是rejected对象,由于Promise中的状态一旦改变就不能更改,因此promise2没办法转成回调函数返回的rejected状态,产生矛盾。
每一个promise.then()都返回一个新的promise,返回的新的promise会在上一个状态发生改变后执行,依此类推,每次返回的新的promise被挂在上一个promise上,造成一个串。
function promiseAll(promises){ if(!Array.isArray(promises)){ throw new TypeError('argument must be array'); } return new Promise((resolve,reject)=>{ let len=promises.length; let index=0; var res=[]; for(let i=0;i<len;i++){//注意定义为let,保留当前的i promises[i].then((data)=>{//执行参数中的promise实例 index++; res[i]=data;//把每个promise的执行成功结果放入数组中 不能用push()方法,保证返回的顺序 if(index==len){//说明全部promise都执行成功 return resolve(res); } },function(err){ return reject(err); }) } }) } var a1=Promise.resolve(1); var a2=Promise.resolve(2); var a3=new Promise((resolve,reject)=>{ resolve('a3'); }) promiseAll([a1,a2,a3]).then(function(res){ console.log(res);//[1,2,'a3'] }).catch(function(err){ console.log(err); })
function Race(promise){ return new Promise((resolve,reject)=>{ for(let i=0;i<promise.length;i++){ promise[i].then(resolve,reject); } }) } var p1=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('2'); },2000) }) var p2=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('3'); },1000) }) Race([p1,p2]).then(function(val){ console.log(val);//3 })
题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用 Promse 实现)
咱们想要在resolve的状况下中断或者终止链式调用,主要方法是基于Promise的特色:原Promise对象的状态和新对象保持一致。
咱们仅须要在链式调用中,返回一个pending状态或者rejected状态的Promise对象便可。
return (new Promise((resolve, reject)=>{}));//返回pending状态
return (new Promise((resolve, reject)=>{rejcet()}));//返回reject状态 会被最后catch捕获。
.then((number)=>{ console.info(’在这里中断‘); return new Promise((resolve,reject)=>{})//pending状态 //或者 return new Promise((resolve,reject)=>{reject()})//reject()状态 }).then((number)=>{ console.info(`fun4 result:${number}`); })