期约与异步编程
失败处理
function double(value,success,failure) {
setTimeout(()=>{
try {
if (typeof value!=='number'){
throw new Error('参数必须为数字');
}
success(value*2);
}catch (e) {
failure(e);
}
},1000);
}
const successCallback=(x)=>console.log(`Success: ${x}`);
const failureCallback=(e)=>console.log(`Failure: ${e}`);
double(10,successCallback,failureCallback);
double('b',successCallback,failureCallback)
这种模式已经不可取了,因为必须在初始化异步操作时定义回调。异步函数的返回值只在短时间内存在,只有预备好将这个短时间内存在的值作为参数的回调才能接收到它。
期约(Promise)
//使用
let p=new Promise(()=>{})
setTimeout(console.log,0,p);
有三种状态:
- 待定(pending)
- 兑现(fulfilled,有时候也称为“解决”,resolved)
- 拒绝(rejected)
待定时i期约的最初使状态。在待定状态下,期约可以落定为代表成功的兑现状态,或者代表失败的拒绝状态。无论落定为那种状态都是不可逆的。只要从待定状态转换为兑现或拒绝,期约的状态就不再改变。而且,也不能保证期约必然会脱离待定状态。
重要的是,期约的状态是私有的,不能直接通过JS检测到。这主要是为了避免根据读取到的期约状态,以同步方式处理期约对象。另外,期约的状态也不能被外部JS代码修改。
Promise.resolve()
//Promise.resolve
let p1=new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('hello')
},1000)
})
let p2=Promise.resolve()
//上述的两个期约实例效果一样
这个解决的期约的值对应着传递给Promise.resolve()
的第一个参数。使用这个静态方法,实际上,可以把任何值都转换为一个期约:
setTimeout(console.log,0,Promise.resolve());
setTimeout(console.log,0,Promise.resolve(3));
setTimeout(console.log,0,Promise.resolve(3,4,5));//多余的参数会被忽略
注意这个静态方法能够包装任何非期约值,包括错误对象,并将其转换为解决的期约。
Promise.reject()
会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能被try/catch
捕获,而只能通过拒绝处理程序捕获)
let p3=new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('hello')
},1000)
})
let p4=Promise.reject(Promise.resolve());
与resolve()
不同,如果传给他一个期约对象,则这个期约会成为它返回的拒绝期约的理由。
Promise.prototype.then()
为期约实例添加处理程序的主要方法。这个then()
方法接收最多两个参数:onResolved
处理程序和onRejected
处理程序。这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。
//.then()
function onResolve(id) {
setTimeout(console.log,0,id,'resolve');
}
function onRejected(id){
setTimeout(console.log,0,id,'reject');
}
let p1=new Promise((resolve,reject)=>setTimeout(resolve,3000));
let p2=new Promise((resolve,reject)=>setTimeout(reject,3000));
p1.then(()=>onResolve('p1'),()=>onRejected('p1'));
p2.then(()=>onResolve('p2'),()=>onRejected('p2'));
Promise.prototype.catch()
用于给期约添加拒绝处理程序。这个方法只接收一个参数:onRejected
处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用Promise.prototype.then(null,onRejected)
Promise.prototype.catch()//返回一个新的期约实例
Promise.prototype.finally()
方法用于给期约添加onFinally
处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。
onFinally
被设计为一个状态无关的方法,所以大多数情况下它将表现为父期约的传递。
非重入期约方法
在整个处理程序中。当期约进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行。同步代码一定会在处理程序之前先执行。
let p=Promise.resolve();
p.then(()=>console.log('onResolved'))
console.log('then() returns');
跟在then()
后面的同步代码一定先于处理程序执行。
如果添加处理程序后,同步代码才改变期约状态,那么处理程序仍然会基于该状态变化表现出非重入特性。下面的例子展示了即使先添加了onResolved
处理程序,再同步调用resolved
,处理程序也不会进入同步线程执行。
let synchronousResolved;
let p=new Promise((resolve, reject)=>{
synchronousResolved=function () {
console.log('1:invoking resolve()');
resolve();
console.log('2:resolve() return')
}
});
p.then(()=>console.log('4:then() handler executes'));
synchronousResolved();
console.log('3:synchronousResolve() return');
//执行结果
//1:invoking resolve()
//2:resolve() return
//3:synchronousResolve() return
//4:then() handler executes
在这个例子中,即使期约状态变化发生在添加处理程序之后,处理程序也会等到运行的消息队列让他出列时才会执行。
拒绝期约与拒绝错误处理
拒绝期约类似于throw()
表达式,因为它们都代表一种程序状态,即需要中断或特殊处理。在期约的执行函数或处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由。
onRejected
处理程序的任务应该是在捕获异步错误之后返回一个解决的期约。
Promise.all()和promise.race()
Promise
类提供两个将多个期约实例组合成一个期约的静态方法:Promise.all()
和Promise.race()
。而合成后期约的行为取决于内部期约的行为。
Promise.all()
此方法创建的期约会在一组期约全部解决之后再解决。此静态方法接收一个可迭代对象,返回一个新期约。
//Promise.all()
let p1=Promise.all([
Promise.resolve(),
Promise.resolve()
]);
//可迭代对象中的元素会通过Promise.resolve转换为期约
let p2=Promise.all([3,4]);
//空的可迭代对象等价于Promise.resolve()
let p3=Promise.all([]);
合成的期约只会在每个包含的期约都解决之后才解决。
let p=Promise.all([
Promise.resolve(),
new Promise((resolve, reject) =>setTimeout(resolve, 1000))
]);
setTimeout(console.log,0,p);
p.then(()=>setTimeout(console.log,0,'all() resolve!'))
如果所有期约都成功解决,则合成期约的解决值就是所有包含期约解决值的数组,按照迭代器顺序。
如果有期约拒绝,则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由。之后再拒绝的期约不会影响最终期约的拒绝理由。
Promise.race()
静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。这个方法接收一个可迭代对象,返回一个新期约。
用法和Promise.all()
基本一致
串行期约合成
到目前为止,我们讨论期约连锁一直围绕期约的串行执行,忽略了期约的另一个主要特性:异步产生值并将其传给处理程序。基于后续期约使用之前期约的返回值来串联期约是期约的基本功能。
function addTwo(x){return x+2};
function addThree(x){return x+3};
function addFour(x){return x+4};
function addTen(x){
return Promise.resolve(x)
.then(addTwo)
.then(addThree)
.then(addFour);
}
addTen(8).then(console.log);
//可以简化成
function compose(...funs){
return [addTwo, addThree, addFour]
.reduce((promise,fn) => promise.then(fn),Promise.resolve(x))
}
let addTen = compose(addTwo,addThree,addFour);
addTen(8).then(console.log);
- 本文链接:https://archer-lan.github.io/2023/11/20/JS-%E6%9C%9F%E7%BA%A6%E4%B8%8E%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。