初探Promise 中断与异常传送
Promise
Promise是JavaScript ES6对于异步任务的解决方案
从语法上来说,Promise是一个构造函数,通过new关键字来新建对象
从功能上来说,Promise用来封装一个异步操作,无论异步操作是成功或失败,Promise都将承诺给你返回一个确切的答案,一个异步任务最终执行的结果。
为什么要有promise
先谈谈为什么要有异步。js单线程,一次只能执行一个任务,有些任务是耗时的,比如图片加载,网络请求等,这时候如果同步执行的话,效率会非常低下。此时就需要异步来处理了。
异步主要是解决了同步阻塞的情况。但是在异步处理中也出现了很多问题,于是ES6对这一问题提出了解决方案---Promise
1 指定回调函数的方式更加灵活
旧的方式:必须在启动异步任务前指定回调函数
promise:启动异步任务-->返回promise对象-->给promise对象绑定回调函数(甚至可以在异步开始前或结束后指定,通过then方法)
你可以先改变状态,再指定then中的回调去处理Promise的结果,那么当状态改变,then中的回调就会执行。
你也可以先指定then中的回调,那么当状态改变时,then中的回调就会执行。
而传统方式必须在启动异步任务时就指定好要去调用的回调函数
const promise = new Promise((resolve, reject) => { //executor执行器函数,同步的 console.log(‘excutor‘) setTimeout(() => { let date = Date.now(); if (date % 2 === 0) { resolve(date) } else { reject(date) } }, 1000) }) console.log(‘创建promise之后‘) //在创建promise之后指定promise的回调函数,onResolved和onRejected //所谓的promise更加灵活,就是指你可以在创建promise之后再通过then这样的api去指定promise的回调函数 //而传统方式必须在启动异步任务前就指定好要调用的回调函数 setTimeout(() => { promise.then( (value) => { console.log("成功" + value)//onFulfiled }, (reason) => { console.log("失败" + reason)//onRejected } ) }, 3000)
2 解决回调地狱问题
回调地狱:或者说回调金字塔。就是在异步的回调函数里面开启下一个异步操作,执行下一个异步函数。多个串联的异步操作。
promise可以解决回调金字塔的问题,或者通俗一点说,就是通过链式编程解决了回调函数嵌套过多的问题,回调地狱不便于阅读,也不利于异常处理
举个例子,要想实现图片的预加载,且保证先后顺序一致,那么就必须结合onload事件,在这个事件的回调函数里面再改变src从而加载下一张图片。要实现这个功能,你的构思可能如下
let base = 100; let img = new Image() img.src = ‘./img/icon-0.png‘; img.onload = function () { console.log(base += img.width); let img1 = new Image() img1.src = ‘./img/icon-1.png‘; img1.onload = function () { console.log(base += img1.width); let img2 = new Image(); img2.src = ‘./img/icon-2.png‘; img2.onload = function () { console.log(base += img2.width); let img3 = new Image(); img3.src = ‘./img/icon-3.png‘; } } }
以上代码能不能解决预加载问题?当然可以。但是问题也同样显著存在,那就是如果图片数量级过大,就会造成函数嵌套过深的问题,也就是上面提到的回调金字塔。
Promise很好地解决了这个问题。
Promise 处理图片预加载问题
function loadImage(src) { return new Promise(function (resolve, reject) { let img = new Image(); img.src = src; img.onload = function () { resolve(img) } img.onerror = function () { reject(img.src, ‘加载失败‘) } }) } //预加载多张图片,采用promise的then返回一个新的promise实现链式编程 loadImage(‘./img/icon-0.png‘) .then(function (img) { console.log(img) return loadImage(‘./img/icon-1.png‘) }, function (src) { }) .then(function (img) { console.log(img) return loadImage(‘./img/icon-2.png‘) }, () => { }) .then(function (img) { console.log(img) }, () => { })
关于回调函数
即然Promise旨在解决回调地狱问题,那么就不得不谈谈回调函数
回调函数有两种类型,一种是同步回调,一种是异步回调
同步回调函数与异步回调函数
同步回调
立即执行,完全执行完了才结束,不会放入回调队列中
例如:数组遍历相关的回调函数或Promise的excutor函数
异步回调
不会立即执行,会放入回调队列中执行
例如:定时器回调/ajax回调/promise成功/失败的回调
//同步回调,一些数组方法如foreach、map let arr = [1, 2, 3] arr.forEach(v => console.log(v)) arr.map(v => console.log(v)) console.log(‘after forEach‘) /* 1 2 3 after forEach */ //异步回调,定时器回调、ajax回调、promise成功或失败的回调 setTimeout(() => { console.log(‘timeout‘) }, 0) console.log(‘after timeout‘) /* 0 timeout */
Primise的状态与状态改变
- pending 等待
- resolved 成功
- rejected 失败
状态改变只能由一次,且只能由pending转变为resolved或rejected
成功时返回的值叫做value,失败时返回的值叫做reason
状态改变
- 执行resolve将pending改为resolved
- 执行reject将pending改为rejected
- 抛出异常,pending会变为rejected,然而,reject并不意味着程序一定有异常(catch&reject)
Promise流程与执行顺序
流程
通过Promise构造函数new一个Promise对象,传一个执行器函数(启动是同步的)作为形参,在这个函数中执行异步操作
如果成功了,执行resolve,Promise对象变为resolved状态,通过then回调onResolved(onFulfild)拿到value,并返回一个新的Promise对象
如果失败了,执行reject,同时promise对象变为rejected状态,通过then或catch回调onRejected()拿到reason,并返回一个新的Promise对象
返回一个新的promise对象可以做链式编程操作
顺序
执行器函数同步执行,resolve和then异步执行
因此打印顺序是:
executor promise then 1
const p = new Promise((resolve, reject) => { console.log(‘executor‘) resolve(1) }) console.log(‘promise ~~‘) p.then( value => { console.log(value) },//onFulfiled reason => { }//onRejected ) console.log(‘then~~‘)
Promise API
- Promise构造函数 Promise(executor(resolve,reject){ //三个状态})
- Promise.prototype.then then本身是同步的,它返回一个promise,then中的回调函数是异步回调函数,
- Promise.prototype.catch
- Promise.race: (promises=>{}) promises包含n个promise的数组
- Promise.all:(promises=>{}) promises包含n个promise的数组
resolve,reject,race,all是函数属性,构造函数本身就具有的
then,catch是原型属性,是promise实例具有的属性
all和race的异同
同:
都返回一个新的promise对象,都接收一个包含多个peromise对象的数组作为回调函数的形参
异:
all是所有promise全部成功才成功。race,就像单词意思一样,竞赛,不论成功或失败,谁先完成就返回谁的状态
resolve reject then catch的使用
//生成一个成功值是1的promise const p1 = new Promise((resolve,reject)=>{ resolve(1) }) //生成一个成功值是2的promise const p2 = Promise.resolve(2); // console.log(p,p1) //生成一个失败值是3的promise const p3 = Promise.reject(3) p1.then(value=>console.log(value))//1 p2.then(value=>console.log(value))//2 p3.then(null,reason=>console.log(reason))//3 p3.catch(reason=>console.log(reason))//3
all和race的使用
all:所有成功才成功,有一个失败就失败,返回一个新的promise
//生成一个成功值是1的promise const p1 = new Promise((resolve, reject) => { resolve(1) }) //生成一个成功值是2的promise const p2 = Promise.resolve(2); // 生成一个失败值是3的promise const p3 = Promise.reject(3) const pAll = Promise.all([p1, p2]) //打印3,全部成功才成功 pAll.then( (values) => { console.log(values)//如果没有p3,返回[1,2] }, (reason) => { console.log(reason)//如果由p3,返回3 } )
race:竞赛。谁先第一个完成,就返回谁的promis状态
//生成一个成功值是1的promise const p1 = new Promise((resolve, reject) => { resolve(1) }) //生成一个成功值是2的promise const p2 = Promise.resolve(2); // 生成一个失败值是3的promise const p3 = Promise.reject(3) const pRace = Promise.race([p3,p1,p2]) const arrSuc = [p1,p2,p3]//此时p1先完成,成功 const arrFai = [p3,p2,p1]//此时p3先完成,失败 pRace.then( (values)=>{ console.log(values)//1 }, (reason)=>{ console.log(reason)//3 } )
关于Promise的几个疑问
什么时候才得到数据?
then是同步的,then中的回调是异步执行的,并且then的回调在promise状态改变之后才会执行,同时得到promise中异步任务的结果数据。
先改变状态还是先指定回调?这两者的先后顺序?
你可以先改变状态,再指定then中的回调,那么当状态改变,then中的回调就会执行。
你也可以先指定then中的回调,那么当状态改变时,then中的回调就会执行。
第二种情况可以通过在executor中添加定时器来实验
const p = new Promise((resolve, reject) => { setTimeout(() => { console.log(‘executor‘) resolve(1) }, 1000) }) console.log(‘promise ~~‘) p.then( value => { console.log(value) },//onFulfiled reason => { }//onRejected ) console.log(‘then~~‘) // 执行顺序: // promise // then // executor // 1
Promise.then返回的结果状态由什么决定?
简而言之,就是由.then的回调函数执行返回的结果决定
如果抛出异常,新promise为rejected,reason为抛出的异常
如果返回的是非Promise得任意值,新promise变为resolved,value为返回的非promise的任意值
如果返国的是另一个新Promise,此Promise的结果就会称为新promise的结果,这个结果可能是成功或者失败
new Promise((resolve, reject) => { resolve(1) }).then( value => { //1 返回新的promise,这个回调返回新promise就是下个then的回调拿到的promise // return Promise.reject(1) //2 返回非promise的任意值,那么下个then拿到的是返回值,并且默认执行resolve // return 2 // return "aaa" // return 不返回,则默认返回undefined //3 抛出异常,下个then拿到的是抛出的值或对象,执行reject throw 1 }, reason => { } ).then( value => console.log(value), reason => console.log(reason) )
Promise如何串联执行多个操作任务,不论同步异步都保证顺序执行?
通过then返回一个新的promise,然后通过链式调用执行多个同步/异步任务
对于同步任务,直接写即可,对于异步任务,写在Promise里面
promise.all是传入一个包含多个promise的promises数组,全部成功状态才算成功,更倾向于逻辑上的结果
new Promise((resolve, reject) => { resolve(1) }).then( value => { console.log(‘同步任务1‘) return 1 } ).then( value => { console.log(value) return new Promise((resolve, reject) => { setTimeout(() => { console.log(‘异步任务2‘) resolve(2) }, 100) }) } ).then( value => { console.log(value) console.log(‘同步任务3‘) return Promise.resolve(3) } )
Promise异常传送?
不写rejected,then里面默认reason=》{throw reason} 往下抛,或者返回一个失败的promise,Promise.reject()
- 使用then时,可以在最后指定失败的回调
- 前面任何操作出了异常,都会在最后的失败回调中处理
new Promise((resolve, reject) => { resolve(1) }).then( value => { console.log(‘同步任务1 ‘, value) throw 100;//沿着this链式,一直传到末尾的catch } //不写相当于reason => throw reason,下面也是一样 ).then( value => { console.log(value) } ).then( value => { console.log(value) console.log(‘同步任务3‘) return Promise.resolve(3) } ).catch(reason => console.log(reason, ‘~~~‘)) /* 9 promise q4.html:16 同步任务1 1 9 promise q4.html:30 100 "~~~" */
中断Promise链?
使用then时,在中间中断,不再调用后面的回调函数
办法:在回调函数种返回一个pending状态的promise对象 return new Promise(()=>{}), 就是让Promise没有结果,那么then也不再向下执行
new Promise((resolve, reject) => { resolve(1) }).then( value => { console.log(‘同步任务1 ‘, value) return Promise.resolve(3) } ).then( value => { console.log(value) //返回一个不做状态转变的promise,让promise处于pending状态,即可中断 return new Promise(()=>{}) } ).then( value => { console.log(value) console.log(‘同步任务3‘) return Promise.resolve(3) } ) /* 9 promise q4 中断.html:16 同步任务1 1 9 promise q4 中断.html:21 3 */