期约与异步编程

失败处理

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);