javascript进阶知识22 - Promise

89 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情

浏览器事件循环

要说Promise就得先了解浏览器的事件循环,也就是浏览器的执行顺序。

浏览器是单线程的语言,也就是当浏览器在解析js代码时,会将代码放在全局上下文执行栈中,在栈中执行这个js代码时,如果是同步的代码,浏览器就会执行这段代码,但是如果这段代码是异步的,那么浏览器就会将这个异步的代码放入一个队列中,等到执行栈中的所有代码都执行完毕后,浏览器又会从队列中取出一个异步的代码放入执行栈中,再执行。一直循环这个操作。

console.log('aaa');
setTimeout(()=>{
    console.log('bbb')
},1000);
console.log('ccc');

// 'aaa'
// 'ccc'
//等待1秒后  'bbb'

Promise

Promise是ES6之后才加的,它主要为了解决的问题就是之前的回调地狱,也更利于代码维护和阅读。

回调地狱:

setTimeout(function () {  //第一层
    console.log('aaa');
    setTimeout(function () {  //第二程
        console.log('bbb');
        setTimeout(function () {   //第三层
            console.log('ccc');
        }, 1000)
    }, 2000)
}, 3000)

就是我们之后要执行的代码要依赖于之前的代码执行结果,那么我们就得嵌套很多回调函数,如果业务一复杂,就会导致这个代码变得很复杂,也很难维护,可读性也差。

Promise就是为了解决这个问题的。他将多层回调改成了链式调用。

const fn = function(params,delay=1000) {
    let p = new Promise((resolve,reject) => {
        setTimeout(()=>{
            resolve(params)
        },delay)
    });
    return p;
}

fn('aaa',3000).then(data =>{
    console.log(data);
    return fn('bbb',2000)
}).then(data => {
    console.log(data);
    return fn('ccc')
}).then(data => {
    console.log(data)
})

Promise是同步还是异步?

我们看到了Promise的用处,下面我们就总结Promise的用法。

Promise里面的代码是同步的

let p = new Promise((resolve,reject) => {
    console.log('aaa');
    resolve('ok')
})
console.log('bbb');

// 代码输出

image.png

我们发现,是先输出aaa再输出bbb的,也就是说Promise里面是同步的。

resolve与reject

我们发现在Promise里面的回调函数中有两个参数,一个是resolve,一个是reject。

如果我们不再Promise中使用resolve或者reject:

let p = new Promise((resolve,reject)=>{})
console.log(p);

image.png

我们发现p是 <pending> 状态。

如果我们使用resolve:

let p = new Promise((resolve,reject) => {
    resolve('ok')
})
console.log(p);

image.png

我们发现p是 <fullfilled> 状态,且PromiseResult中的值为我们传的参数‘ok’。

如果我们使用reject:

let p = new Promise((resolve,reject) => {
    reject('no')
})
console.log(p);

image.png

我们发现p是 <rejected> 状态,且还有一个报错,且PromiseResult中的值为我们传的参数‘no’。。

从上面的三个输出我们就可以得出结论:resolve()是返回成功的结果,reject()是返回失败的结果。

Promise的状态一旦改变了,就不可再被改变

Promise有三种状态,初始时是 <pending> ,成功后为<fullfilled>,失败后为<rejected>

那么如果Promise已经被改变为fullfied,还可以再被改变为rejected吗?

答案是不可以:

let p = new Promise((resolve,reject) => {
    resolve('ok');
    console.log('aaa')
    reject('no')
})
console.log(p);

image.png

我们可以发现,虽然在resolve以后,代码还可以执行,但是再reject后,promise的状态并没有被改变

then

在Promise中,我们resolve或者reject后,就返回了一个Promise实例,但是我们想得到resolve或者reject中的值,然后继续执行怎么办呢?那就得靠then了

then有两个参数

then有两个参数,一个是处理resolve的函数,一个是处理reject的函数。

let p = new Promise((resolve,reject) => {
    reject('ok')
})
p.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
// 'ok'


let p = new Promise((resolve,reject) => {
    reject('no')
})
p.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
// 'no'

也就是说,第一个参数函数是处理<fullfied>状态的Promise,且函数里面的参数就是Promise的PromiseResult。第二个参数是处理<rejected>状态的Promise,且函数里面的参数就是Promise的PromiseResult。

then是同步还是异步?

我们知道Promise里面的回调函数是同步的,那么then里面的回调函数是同步还是异步的呢?

let p = new Promise((resolve,reject) => {
    reject('ok')
})
p.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
console.log('aaa');

image.png

我们发现,先输出的是'aaa',在输出的是'ok',所以then里面的回调函数是异步的

then也有返回值

let p = new Promise((resolve,reject) => {
    reject('ok')
})
p = p.then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
console.log(p);

image.png

我们发现,then里面也是有返回值的,哪怕我们并没有写return。它默认返回的是一个<fullfilled>状态的Promise实例,返回的值是undefined。

如果我们返回一个常量:

let p = new Promise((resolve,reject) => {
    resolve('ok')
})
p = p.then(res => {
    console.log(res);
    return 'okk'
}, err => {
    console.log(err);
    return 'noo'
})
console.log(p);

image.png

如果我们返回的是其他原始值,返回的是一个<fullfilled>状态的Promise实例,返回的值是我们return 里面的值。

我们知道我们可以使用then来调用Promise实例。

let p = new Promise((resolve,reject) => {
    resolve('ok')
})
p = p.then(res => {
    console.log('resolve:'+res);
    return 'okk'
}, err => {
    console.log('reject:'+err);
    return 'noo'
})
p.then(res => {
    console.log('resolve:'+res);
    return 'okkk'
}, err => {
       console.log('reject:'+err);
    return 'nooo'
})

image.png

let p = new Promise((resolve,reject) => {
    reject('no')
})
p = p.then(res => {
    console.log('resolve:'+res);
    return 'okk'
}, err => {
    console.log('reject:'+err);
    return 'noo'
})
p.then(res => {
    console.log('resolve:'+res);
    return 'okkk'
}, err => {
       console.log('reject:'+err);
    return 'nooo'
})

image.png

我们发现的确是这样的!不管是在resolve还是在reject中返回的都是默认的<fullfilled>状态。

then里面返回Promise

我们知道如果我们返回的不是Promise,那么都默认返回的是<fullfilled>状态,那么如何返回其他状态的呢?

我们可以return一个Promise实例,这个return的Promise是哪种状态,最终返回的就是哪种状态:

let p = new Promise((resolve,reject) => {
    reject('no')
})
p = p.then(res => {
    console.log('resolve:'+res);
    return Promise.reject('noo')
}, err => {
    console.log('reject:'+err);
    return Promise.resolve('okk')
})
p.then(res => {
    console.log('resolve:'+res);
}, err => {
    console.log('reject:'+err);
})

image.png

catch

我们可以对上面的链式调用进行简写,就是使用catch来统一对<rejected>状态进行管理,前面出的任何错误,都会跳在这里进行处理。

let p = new Promise((resolve,reject) => {
    reject('no')
})
p.then(res => {
    console.log('resolve:'+res);
    return Promise.reject('noo')
}).then(res => {
    console.log('resolve:'+res);
}).catch((err) => {
    console.log('err:'+err)
}) 

image.png

let p = new Promise((resolve,reject) => {
    resolve('yes')
})
p.then(res => {
    console.log('resolve:'+res);
    return Promise.reject('noo')
}).then(res => {
    console.log('resolve:'+res);
}).then(res => {
    console.log('resolve:' + res)
}).catch((err) => {
    console.log('err:'+err)
}) 

输出:

image.png

我们发现是直接跳转到最后了,中间的部分就会被跳过!

除此之外,catch还可以捕获这个链式调用中的错误,然后让代码继续运行,不至于堵塞。

let p = new Promise((resolve,reject) => {
    resolve('yes')
})
p.then(res => {
    console.log('resolve:'+res);
    console.log(a);
    return Promise.reject('noo')
}).then(res => {
    console.log('resolve:'+res);
}).catch((err) => {
    console.log('err:'+err)
})

image.png

我们发现在第一个then里面,我们输出了一个变量a,但是a并没有被定义,如果我们不对这个错误进行捕获,那么这段代码就会卡在错误那里,不再继续往下执行。

功能等同于try/catch.

catch也是有返回值的

let p = new Promise((resolve,reject) => {
    resolve('yes')
})
p = p.then(res => {
    console.log('resolve:'+res + a);
}).then(res => {
    console.log('resolve:'+res);
}).catch((err) => {
    console.log('err:'+err)
})
console.log('pp:'+p);

输出:

image.png

返回的也是默认的<fullfilled>状态的Promise实例。所以也还可以继续往下进行then调用。

Promise.all()

Promise.all()接收一个参数,这个参数就是数组,数组里面的元素都得是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理.

let p = Promise.all([p1,p2,p3]);

p的状态由p1、p2、p3决定,分成两种情况:

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
let p1 = new Promise((res,err) => {
    setTimeout(()=>{
        res('p1')
    },3000)
});
let p2 = new Promise((res,err) => {
    setTimeout(()=>{
        res('p2')
    },2000)
})
let p3 = new Promise((res,err) => {
    setTimeout(()=>{
        res('p3')
    },1000)
})
let p = Promise.all([p1,p2,p3]);
p.then(data => {
    console.log(data)
},err => {
    console.log(err)
})

image.png

我们发现只有等3s后,才会输出['p1','p2','p3']。

Promise.race()

类似Promise.all,但是区别就是Promise.all是要将参数中所有的实例都返回状态后,Promise.all的状态才会改变。而Promise.race是有任意一个完成,那么这个Promise.race的状态就会改变.

let p1 = new Promise((res,err) => {
    setTimeout(()=>{
        res('p1')
    },3000)
});
let p2 = new Promise((res,err) => {
    setTimeout(()=>{
        res('p2')
    },2000)
})
let p3 = new Promise((res,err) => {
    setTimeout(()=>{
        res('p3')
    },1000)
})
let p = Promise.race([p1,p2,p3]);
p.then(data => {
    console.log(data)
},err => {
    console.log(err)
})

image.png

1S后输出了'p3',而Promise.all是3S后输出。