promise的作用与理解
promise的理解与使用
promise是什么
1. 理解promise
抽象表达:Promise是JS中进行异步编程的新的解决方案(旧方案是单纯使用回调函数)
---- 异步编程 ①fs 文件操作 ②数据库操作 ③Ajax ④定时器
具体表达:
①从语法上看:Promise是一个构造函数 (自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法)
②从功能上看:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
阮一峰的解释:
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
2. Promise 的状态
实例对象promise中的一个属性 PromiseState
1.pending 变为 resolved/fullfilled
2.pending 变为 rejected
注意:
对象的状态不受外界影响
只有这两种,且一个 promise 对象只能改变一次
一旦状态改变,就不会再变,任何时候都可以得到这个结果
无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,而失败的一般称为 reason。
3. Promise对象的值
实例对象promise的另一个值 PromiseResult
保存着对象 成功/失败 的值(value/reason)
resolve/reject可以修改值
4. Promise 的基本流程
5. Promise 的基本使用
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(reason);
}
});
Promise构造函数接受一个函数(执行器函数)作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数value传递出去;
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数error/reason传递出去。
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
promise.then(function(value) {
// success
}, function(reason) {
// failure
});
then方法可以接受两个回调函数作为参数。
第一个回调函数onResolved()是Promise对象的状态变为resolved时调用
第二个回调函数onRejected()是Promise对象的状态变为rejected时调用
这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数
案例:
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + (m - 1)
}
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const a = rand(1, 100)
if (a <= 30) {
resolve(a)
} else {
reject(a)
}
}, 1000)
})
console.log(p);
p.then((value) => {
alert("恭喜你中奖了,你的号码是"+value)
}, (reson) => {
alert("再接再厉,你的号码是:"+reson)
})
})
.then() 和执行器(executor)同步执行,.then() 中的回调函数异步执行
为什么要用 Promise
1. 指定回调函数的方式更加灵活
旧的:必须在启动异步任务前指定
// 1. 纯回调的形式
// 成功的回调函数
function successCallback(result) {
console.log("声音文件创建成功:" + result);
}
// 失败的回调函数
function failureCallback(error) {
console.log("声音文件创建失败:" + error);
}
// 必须先指定回调函数,再执行异步任务
createAudioFileAsync(audioSettings, successCallback, failureCallback) // 回调函数在执行异步任务(函数)前就要指定
promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
// 2. 使用Promise
const promise = createAudioFileAsync(audioSettings); // 执行2秒
setTimeout(() => {
promise.then(successCallback, failureCallback) // 也可以获取
}, 3000);
2. 支持链式调用,可以解决回调地狱问题
什么是回调地狱?
回调函数嵌套调用,外部回调函数异步执行的结果是其内部嵌套的回调函数执行的条件
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result:' + finalResult)
}, failureCallback)
}, failureCallback)
}, failureCallback)
回调地狱的缺点?
- 不便于阅读
- 不便于异常处理
解决方案?
- promise 链式调用
使用 promise 的链式调用解决回调地狱
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {console.log('Got the final result:' + finalResult)})
.catch(failureCallback)
终极解决方案?
- async/await
回调地狱的终极解决方案 async/await
async function request() {
try{
const result = await doSomething()
const newResult = await doSomethingElse(result)
const finalResult = await doThirdThing(newResult)
console.log('Got the final result:' + finalResult)
} catch (error) {
failureCallback(error)
}
}
如何使用 Promise
Promise 构造函数:Promise(executor) {}
executor 函数:同步执行 (resolve, reject) => {}
resolve 函数:内部定义成功时调用的函数 resove(value)
reject 函数:内部定义失败时调用的函数 reject(reason)
说明:executor 是执行器,会在 Promise 内部立即同步回调,异步操作 resolve/reject 就在 executor 中执行
Promise.prototype.then 方法:p.then(onResolved, onRejected)
指定两个回调(成功+失败)
onResolved 函数:成功的回调函数 (value) => {}
onRejected 函数:失败的回调函数 (reason) => {}
说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的 promise 对象
Promise.prototype.catch 方法:p.catch(onRejected)
指定失败的回调
1)onRejected
函数:失败的回调函数 (reason) => {}
说明:这是then()
的语法糖,相当于 then(undefined, onRejected)
new Promise((resolve, reject) => { // excutor执行器函数
setTimeout(() => {
if(...) {
resolve('成功的数据') // resolve()函数
} else {
reject('失败的数据') //reject()函数
}
}, 1000)
}).then(
value => { // onResolved()函数
console.log(value) // 成功的数据
}
).catch(
reason => { // onRejected()函数
console.log(reason) // 失败的数据
}
)
Promise.resolve 方法:Promise.resolve(value)
value:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象
返回:返回一个带着给定值解析过的 Promise 对象,如果参数本身就是一个 Promise 对象,则直接返回这个 Promise 对象。
如果传入的参数为 非Promise类型的对象, 则返回的结果为成功promise对象
let p1 = Promise.resolve(521);
console.log(p1); // Promise {<fulfilled>: 521}
如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK'); // 成功的Promise
reject('Error');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
Promise.reject 方法:Promise.resolve(reason)
reason
:失败的原因
说明:返回一个失败的 promise
对象
let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p);
console.log(p2);
console.log(p3);
- Promise.resolve()/Promise.reject() 方法就是一个语法糖
- 用来快速得到Promise对象
//产生一个成功值为1的promise对象
new Promise((resolve, reject) => {
resolve(1)
})
//相当于
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
p1.then(value => {console.log(value)}) // 1
p2.then(value => {console.log(value)}) // 2
p3.catch(reason => {console.log(reason)}) // 3
Promise.all 方法:Promise.all(iterable)
iterable
:包含 n 个 promise
的可迭代对象,如 Array
或 String
说明:返回一个新的 promise
,只有所有的 promise
都成功才成功,只要有一个失败了就直接失败
<script>
//promise.all必须全部promise对象返回的结果是真,结果才会为真
const p1 = new Promise((resolve, reject) => {
// reject("erro")
setTimeout(() => {
resolve("success")
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
resolve("ok")
// reject("erro")
})
const p3 = new Promise((resolve, reject) => {
resolve("ok")
// reject("erro")
})
const p4 = Promise.all([p1, p2, p3])
console.log(p4);
</script>
Promise.race方法:Promise.race(iterable)
iterable
:包含 n 个 promise
的可迭代对象,如 Array
或 String
说明:返回一个新的 promise
,第一个完成的 promise
的结果状态就是最终的结果状态谁先完成就输出谁(不管是成功还是失败)
const pRace = Promise.race([p1, p2, p3])
// 谁先完成就输出谁(不管是成功还是失败)
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
pRace.then(
value => {
console.log('race onResolved()', value)
},
reason => {
console.log('race onRejected()', reason)
}
)
//race onResolved() 2
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result);
Promise 的几个关键问题
如何改变 promise 的状态?
(1)resolve(value)
:如果当前是 pending
就会变为 resolved
(2)reject(reason)
:如果当前是 pending
就会变为 rejected
(3)抛出异常:如果当前是 pending
就会变为 rejected
const p = new Promise((resolve, reject) => {
//resolve(1) // promise变为resolved成功状态
//reject(2) // promise变为rejected失败状态
throw new Error('出错了') // 抛出异常,promise变为rejected失败状态,reason为抛出的error
})
p.then(
value => {},
reason => {console.log('reason',reason)}
)
// reason Error:出错了
一个 promise 指定多个成功/失败回调函数,都会调用吗?
当 promise改变为对应状态时都会调用
const p = new Promise((resolve, reject) => {
//resolve(1)
reject(2)
})
p.then(
value => {},
reason => {console.log('reason',reason)}
)
p.then(
value => {},
reason => {console.log('reason2',reason)}
)
// reason 2
// reason2 2
改变 promise 状态和指定回调函数谁先谁后?
都有可能,常规是先指定回调再改变状态,但也可以先改状态再指定回调
- 如何先改状态再指定回调?
(1)在执行器中直接调用 resolve()
/reject()
(2)延迟更长时间才调用 then()
let p = new Promise((resolve, reject) => {
resolve('OK');//这个时候会先改变状态在指定回调
setTimeout(()=>{
resolve("ok") //这个时候先指定回调在改变状态
})
});
p.then(value => {
console.log(value);
},reason=>{
})
- 什么时候才能得到数据?
(1)如果先指定的回调,那当状态发生改变时,回调函数就会调用得到数据
(2)如果先改变的状态,那当指定回调时,回调函数就会调用得到数据
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) // 改变状态
}, 1000)
}).then( // 指定回调函数 (先指定)
value => {},
reason =>{}
)
此时,先指定回调函数,保存当前指定的回调函数;后改变状态(同时指定数据),然后异步执行之前保存的回调函数。
new Promise((resolve, reject) => {
resolve(1) // 改变状态
}).then( // 指定回调函数
value => {},
reason =>{}
)
这种写法,先改变的状态(同时指定数据),后指定回调函数(不需要再保存),直接异步执行回调函数
promise.then() 返回的新 promise 的结果状态由什么决定?
(1)简单表达:由 then()指定的回调函数执行的结果决定
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
}, reason => {
console.warn(reason);
});
console.log(result);
因为返回的是undefined
(2)详细表达:
① 如果抛出异常,新 promise
变为 rejected
,reason
为抛出的异常
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
//1. 抛出错误
throw '出了问题';
}, reason => {
console.warn(reason);
});
console.log(result);
② 如果返回的是非 promise 的任意值,新 promise变为 resolved,value为返回的值
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
//2. 返回结果是非 Promise 类型的对象
return 521;
}, reason => {
console.warn(reason);
});
console.log(result);
③ 如果返回的是另一个新 promise,此 promise的结果就会成为新 promise的结果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
//3. 返回结果是 Promise 对象
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
});
}, reason => {
console.warn(reason);
});
console.log(result);
promise 如何串联多个操作任务?
(1)promise
的 then()
返回一个新的 promise
,可以并成 then()
的链式调用
(2)通过 then
的链式调用串联多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value); // success
}).then(value => {
console.log(value); // undefined
})
Promise 异常穿透(传透)?
(1)当使用 promise
的 then
链式调用时,可以在最后指定失败的回调
(2)前面任何操作出了异常,都会传到最后失败的回调中处理
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(
reason => {
console.log('onRejected1()', reason)
}
)
// onRejected1() 1
相当于这种写法:多写了很多reason => {throw reason}
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
},
reason => {throw reason} // 抛出失败的结果reason
).then(
value => {
console.log('onResolved2()', value)
return 3
},
reason => {throw reason} // 抛出失败的结果reason
).then(
value => {
console.log('onResolved3()', value)
},
reason => {throw reason} // 抛出失败的结果reason
).catch(
reason => {
console.log('onRejected1()', reason)
}
)
// onRejected1() 1
所以失败的结果是一层一层处理下来的,最后传递到 catch
中。
或者,将 reason => {throw reason}
替换为 reason => Promise.reject(reason)
也是一样的。
中断 promise 链?
当使用 promise
的 then
链式调用时,在中间中断,不再调用后面的回调函数
办法:在回调函数中返回一个 pending
状态的 promise
对象
new Promise((resolve, reject) => {
//resolve(1)
reject(1)
}).then(
value => {
console.log('onResolved1()', value)
return 2
}
).then(
value => {
console.log('onResolved2()', value)
return 3
}
).then(
value => {
console.log('onResolved3()', value)
}
).catch(
reason => {
console.log('onRejected1()', reason)
}
).then(
value => {
console.log('onResolved4()', value)
},
reason => {
console.log('onRejected2()', reason)
}
)
// onRejected1() 1
// onResolved4() undefined
在 catch
中返回一个新的 promise
,且这个 promise
没有结果。
由于,返回的新的 promise
结果决定了后面 then
中的结果,所以后面的 then
中也没有结果。
这就实现了中断 promise链
的效果。
async await
aysnc函数
1.函数的返回值为promise对象
2.promise对象得结果由async函数执行得返回值决定
<script>
async function main(){
}
const m = main()
console.log(m);
</script>
<script>
async function main() {
//如果返回的是一个非promise对象 返回结果为成功得promise
// return 123
//如果返回的是promise对象 返回结果由promise对象得返回结果决定
return new Promise((resolve, reject) => {
reject("no")
})
}
const m = main()
console.log(m);
</script>
await函数
1.await右侧得表达式一般为promise对象,但也可以是其他得值
2.如果表达式是promise对象,await返回的是promise成功得值
3.如果表达式是其他值,直接将此作为await得返回值
4.await必须搭配async使用
5.如果await得promise失败,就会抛出异常,需要用try catch 捕获异常
<script>
async function main() {
const p = new Promise((resolve, reject) => {
// resolve("ok")
reject("error")
})
//为成功的promise对象
// let a = await p
// console.log(a);
// //为其他类型
// let b = await 123
// console.log(b);
try {
let c = await p
} catch (e) {
console.log(e);
}
}
const m = main()
// console.log(m);
</script>
async await 结合发送Ajax请求
<button id="btn">发送Ajax请求</button>
<script>
function sendAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('Get', url)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject(xhr.status)
}
}
}
}).then(value => console.log(value), reson => console.log(reson))
}
const btn = document.querySelector("#btn")
btn.addEventListener("click", async function () {
let data = await sendAjax("https??api.apiopen.top/getJoke")
console.log(data);
})
</script>
js异步之宏队列与微队列
原理图:
<script>
setTimeout(()=>{
console.log(1);
Promise.resolve(5).then(v=>{
console.log(v);
})
},0)
setTimeout(()=>{
console.log(4);
},0)
Promise.resolve(2).then(v=>{
console.log(v);
})
Promise.resolve(3).then(v=>{
console.log(v);
})
</script>
1.js中用来存储待执行回调函数的队列包含2个不同的特定的队列
2.宏队列:用来保存待执行的宏任务(回调),比如:定时器 / DOM事件回调/ ajax回调
3.微队列:用来保存待执行的微任务(回调),比如:promise的回调/MutationObserver的回调
4.js执行时会区别这2个队列
面试题:
1.<script>
setTimeout(() => {
console.log(1);
})
Promise.resolve().then(() => {
console.log(2);
})
Promise.resolve().then(() => {
console.log(4);
})
console.log(3);
//3 2 4 1
</script>
2.<script>
setTimeout(() => {
console.log(1);
})
new Promise((resolve) => {
console.log(2);
resolve()
}).then(() => {
console.log(3);
}).then(() => {
console.log(4);
})
console.log(5);
//2 5 3 4 1
</script>
3.<script>
const first = () => (
new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6)
}, 0)
resolve(1)
})
resolve(2)
p.then((arg) => {
console.log(arg);
})
})
)
first().then((arg) => {
console.log(arg);
})
console.log(4);
//3 7 4 1 2 5
</script>
4.<script>
setTimeout(() => {
console.log(0);
})
new Promise((resolve, reject) => {
console.log(1);
resolve()
}).then(() => {
console.log(2);
new Promise((resolve, reject) => {
console.log(3);
resolve()
}).then(() => {
console.log(4);
}).then(() => {
console.log(5);
})
}).then(() => {
console.log(6);
})
new Promise((resolve, reject) => {
console.log(7);
resolve()
}).then(() => {
console.log(8);
})
//1 7 2 3 8 4 6 5 0
</script>