初探Promise 中断与异常传送
Posted ltfxy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初探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
*/
以上是关于初探Promise 中断与异常传送的主要内容,如果未能解决你的问题,请参考以下文章
VSCode自定义代码片段12——JavaScript的Promise对象