了解 Promise ?
Promise 是一种异步编程的解决方案;有三种状态,pending (进行中)、resolved(已完成)、rejected(已失败)。当Promise的状态由 pending 转变为 resolved 或 rejected 时,会执行相应的方法。状态一旦改变,就无法再次改变状态。
特点:Promise 的特点是只有异步操作的结果,可以决定当前是哪一种状态,其他操作都无法改变这个状态。
Promise 解决的痛点是什么 ?
- 多次异步调用的结果,顺序可能不同步
- 异步调用的结果如果存在依赖,则需要嵌套
在ES5中,当进行多层嵌套回调时,会导致代码层次过多,很难进行维护和二次开发;而且会导致回调地狱的问题。
- 解决了 回调地狱 的问题;
- **promise 可以支持多个并发的请求,获取并发请求中的数据 **
- promise 可以解决可读性的问题
- promise 可以解决信任问题;对于回调过早、回调过晚或没有调用和回调次数天少或太多,由于promise只能决议一次,决议值只能有一个,决议之后无法改变,任何then中的回调也只会被调用一次,所以这就保证了Promise可以解决信任问题
Promise 解决的痛点还有其他方法可以解决吗? 如果有,请举例
**Promise 解决的通电还有其他方法可以解决,比如 setTimeout,事件监听,回调函数,Generator函数,async/await **
- setTimeout:缺点不精确,只能确保在一定时间后加入到任务队列,并不保证立马执行。只有执行引擎栈中的代码执行完毕,主线程才会取读取任务队列
- 事件监听: 任务的执行不取决于代码的顺序,而取决于某个事件是否发生
- Generator函数:虽然将异步操作表示的很简洁,但是流程管理却不方便(即何时执行第一阶段,何时执行第二阶段).
Promise 的问题? 解决办法?
问题
- promise 一旦执行,无法中途取消
- promise 的错误无法在外部被捕捉到,只能在内部进行预判处理
- promise 的内部如何执行,监测起来很难
解决办法
使用ES7引入的 async,await 来处理异步
Promise 语法
new Promise(function(resolve, reject){
//...
})
Promise 的构造函数接收一个参数,是函数,并且传入两个参数 resolve 和 reject,分别表示异步操作执行成功后的回调函数 和 异步操作执行失败后的回调函数。
Promise 的基本使用
// 封装
function returnAsync() {
var promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
console.log("执行完成")
let num = Math.ceil(Math.random()*10)
if( num <= 5 ) {
resolve(num); // 成功
} else {
reject("数字太大") // 失败
}
}, 1000)
})
return promise
}
// 使用方式一,在then添加两个参数,第一个表示成功回调,第二个表示失败回调
returnAsync()
.then((data) => {
console.log("成功回调", data)
console.log(newnum); // 此处的newnum未定义
// 使用第二个参数作为异常回调,会报错卡死JS
}, (error) => {
console.log("失败回调", error)
})
// 使用方式二,catch的用法 (当then中有代码抛出异常时,并不会报错卡死js,而是会进到catch方法中,相当于我们的tyr/catch语句功能)
returnAsync()
.then((data) => {
console.log("成功回调", data)
console.log(newnum); // 此处的newnum未定义
})
.catch((error) => {
console.log("失败回调", error)
})
.finally(() => {
console.log("无论成功失败都会执行")
})
Promise 多次处理异步请求 (链式调用)
实际开发场景一:
同时请求多个接口,比如:在请求完 接口1 之后,需要根据接口1的数据继续请求接口2,以此类推...
实际开发场景二:
一个大的区域需要做一个Loading,后端分别给了好几个接口,此时需要全部请求完成之后才移除Loading
// 封装发送请求函数,返回一个Promise
function ajaxQuery(url) {
let promise = new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if(xhr.readyState != 4) return;
if(xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText);
// xhr.responseText 是从接口拿到的数据
} else {
// 处理异常情况
reject("接口请求失败")
}
}
xhr.responseType = 'json'; // 设置返回的数据类型
xhr.open('get', url)
xhr.send(null); // 请求接口
})
return promise;
}
// 使用: 发送多个ajax请求并且保持顺序
ajaxQuery("http://localhost:3000/api1")
.then((data1) => {
// 请求完接口1后,继续请求接口2
return ajaxQuery("http://localhost:3000/api2")
})
.then((data2) => {
// 请求完接口2后,继续请求接口3
return ajaxQuery("http://localhost:3000/api3")
})
.then((data3) => {
// 获取接口3返回的数据
console.log("接口3的数据", data3)
})
.catch((error) => {
console.log("异常:", error)
})
Promise回调函数的返回值
两种情况:
情况一: 返回Promise实例对象,返回的该实例对象会调用下一个 then
Promise.resolve().then(() => 'Hello World!')
.then((value) => {
console.log("fulfilled:", value); // Hello World!
})
.catch((value) => {
console.log("rejected:", value)
})
情况二: 返回普通值,返回的普通值会直接传递给下一个 then
Promise.resolve().then(() => {
return Promise.resolve("Hello")
})
.then((value) => {
console.log("fulfilled:", value) // Hello
})
.catch((value) => {
console.log('rejected:', value)
})
Promise 常用API
Promise.resolve(value)
# 类方法,该方法返回一个以 value 值解析后的 Promise 对象
# 普通值
Promise.resolve([1, 2, 3])
.then((value) => {
console.log(value) // [1,2,3]
})
# 另一个promise
let otherPromise = Promise.resolve(33)
Promise.resolve(otherPromise)
.then((value) => {
console.log(value) // 33
})
Promise.reject()
# 类方法,且与resolve唯一的不同是,返回的promise对象的状态为rejected
function asyncPromise() {
try {
console.log(newNum); // 未定义,会走catch
return Promise.resolve(100);
} catch (error) {
return Promise.reject(error)
}
}
// 使用
asyncPromise()
.then((value) => {
// newNum未定,则走catch,如果去掉这返回 100
console.log(value)
})
.catch((error) => {
// error ReferenceError: newNum is not defined
console.log(error)
})
Promise.prototype.then
实例方法,为Promise注册回调函数,fn().then((value) => {}),value 是上一个任务的返回结果,then中的函数一定要 return 一个结果 或者一个新的 Promise 对象,才可以让之后的 then回调接收
Promise.prototype.catch
实例方法,捕获异常,fn().catch((error) => {}), error 是 catch 注册之前的回调抛出的异常信息
Promise.race()
# 类方法,多个Promise 任务同时执行,返回最先执行结束的 Promise 任务结果,不管这个 Promise 结果是成功还是失败
let p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one')
})
let p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two')
})
Promise.race([p1, p2]).then((value) => {
console.log(value) // 两个都完成 two 最快
})
# 注意:使用场景可以用于 提示网络不佳等
Promise.all()
# 类方法,多个Promise任务同事执行。如果全部完成执行,则以数组的方式返回所有Promise任务的执行结果。只要任何一个输出的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息
let p1 = Promise.resolve(10)
let p2 = 1773
let new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo')
})
Promise.all([p1, p2, p3])
.then((values) => {
console.log(values); // [10, 1773, "foo"]
})
问题:为什么 promise 需要引入微任务?
Promise 中的执行函数是同步进行的,但是里面存在着异步操作,在异步操作结束后会调用 resolve 方法, 或者中途遇到错误调用reject 方法,这两者都是作为微任务进入到 EventLoop 中。但是你有没有想过, Promise 为什么要引入微任务的方式来进行回调操作?
若 promise 回调放在进行宏任务队列的尾部,若队列非常长,那么回调迟迟得不到执行,造成的效果就是应用卡顿。所以就引入了微任务
将回调函数放到当前宏任务中的最后面。
这样,利用微任务解决了两大痛点:
- 采用异步回调替代同步回调解决了浪费 CPU 性能的问题
- 放在当前宏任务最后执行,解决了回调执行的实时性问题