关于Promise的理解和基本用法

343 阅读6分钟

1.Promise的理解

1.1 基本概念

首先,Promise是一种异步编程的解决方案。通俗点讲,就是一个容器,里面存放着我们未来想要进行的事件(通常为一个异步操作)的结果。我们可以通过Promise以同步方法进行异步编程,避免出现多层嵌套回调。

1.2 Promise对象

Promise对象代表一个异步操作,其中包含三种状态:

  1. pending: 异步操作处于进行中
  2. fulfilled: 异步操作已完成
  3. rejected: 异步操作失败

此状态不受外界影响,只有异步操作的结果可以改变该状态。

2.基本用法

Promise是一个构造函数用于生成Promise实例,他所接受的参数是一个函数,该函数拥有resolved和rejected两个参数

下面代码生成了一个Promise实例:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

异步操作成功时resoved函数使当前状态由pending(进行中) => fulfilled(已完成),而失败时rejected函数使当前状态由pending(进行中) => rejected(失败)。

2.1 .then()与.catch()方法

Promise实例生成以后,可以用then方法、catch方法分别指定Resolved状态和Rejected状态的回调函数:

const promise = new Promise(function(resolve,reject) {
    setTimeout(() => {   //这里使用 setTimeout 模拟异步操作
        const a = 1;
        // const a = 2;
        if (a === 1) {
            resolve('a等于'+ a)
        } else {
            reject('a不等于1,a为' + a)
        }
    },1000)
})
promise.then(value => {
    console.log(value) // a等于1
}).catch((err) => {
    console.log(err)
})

// 下方代码then的第二个参数可代替上方catch方法

// promise.then(value => {
//     console.log(value) // a等于1
// }, err => {
//     console.log(err) // a不等于1,a为2
// })

2.2 Promise的链式调用

Promise的优势就在于链式调用,现有多个异步任务需要顺序执行,如果不用Promise则会陷入一层一层的回调函数,也就是常说的“嵌套地狱”。

当我们下一个函数的参数是上一个函数的返回值时,我们的代码就可能出现以下情况: 假设业务开发中有3个接口需要异步执行

function first(param, fun) {
    //延时模拟网络请求
    setTimeout(function() {
        console.log('方法一正在执行');
        let firstResult = param + 1;
        fun(firstResult);//参数依赖first方法的返回值
    },1000)
}

function second(param, fun) {
    setTimeout(function() {
        console.log('方法二正在在执行')
        let secondParam = param + 1;
        console.log('我接受了第一个方法的返回值' + param)
        fun(secondParam);
    },1000)
}

function third(param) {
    setTimeout(function() {
        let finalParam = param + 1;
        console.log('我是最终结果' + param)
    },1000)
}

first(1,function(firstRes) {
    second(firstRes, function(secondRes) {
        third(secondRes)
    })
})

这里我们可以看到,程序顺序输出结果:

image.png

可以看到,因为依赖关系,如果接口数量较多时,将会出现非常深的嵌套,这就形成了所谓的回调地狱。这种横向展开的代码使得代码的可读性变差。

而Promise就能简化这种层层回调的方法。实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,他比直接进行多次回调更见简单、灵活。因此,在遇到上述场景是,正确使用Promise的方法如下:

function first(param) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第一个方法正在执行')
            let firstParam = param + 1;
            resovled(firstParam)
        },1000)
    })
    return promise;
}

function second(data) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('我是第二个方法')
            console.log('我接受了第一个方法的返回值' + data)
            let secondParam = data + 1;
            resovled(secondParam)
        },1000)
    })
    return promise;
}
function third(data) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('我是第三个方法')
             console.log('我接受了第二个方法的返回值' + data)
            resovled(data)
        },1000)
    })
    return promise;
}

first(1).then((value1) => {
    console.log(value1)
    return second(value1)
})
.then((value2) => {
    console.log(value2)
    return third(value2)
})
.then((value3) => {
    console.log(value3)
})

输出结果为:

image.png

从上面的结果我们可以看出,在first方法中传给resovled的值,能在接下来的.then方法中拿到。并且,在then方法中,也可以直接return一个Promise对象,这样在后面的.then中就可以接收到上一个方法中传给resolved的值。

从上述对比中我们可以清楚的看到,虽然Promise并没有对代码量有过多的简化,但是使得原来横向展开的嵌套地狱变为纵向展开,不管有多少个方法需要顺序执行,我们都可以通过 then的调用不停地串连起来使得代码更加清晰,易维护。

2.3 promise的其它方法

2.3.1 all()

Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。从状态上说,只有当下面3个Promise 实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

function first(param) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第一个方法正在执行')
            resovled(1)
        },2000)
    })
    return promise;
}

function second(data) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第二个方法开始执行')
            resovled(2)
        },1000)
    })
    return promise;
}
function third(data) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第三个方法开始执行')
            resovled(3)
        },1000)
    })
    return promise;
}

Promise.all([first(),second(),third()])
.then(([a,b,c])=> {
    console.log('3个方法都执行完毕')
    console.log(a)
    console.log(b)
    console.log(c)
})

结果:

image.png

从结果中我们可以看到first方法执行时间较长,但其它执行较快的操作执行完后,回调函数并未立即执行,而是当3个异步操作的结果都返回后Promise.all方法后面的回调函数才会调用,同时输出 1,2,3。

2.3.1 race()

从字面意思来看,race是竞赛的意思。这样理解起来更加方便。race的作用与all恰恰相反,当只要任意一个异步操作执行完毕时会立即执行.then回调。

但这里需要注意一下,当第一个回调函数执行完毕后,其它的异步操纵并不会停止,而是继续进行。

function first(param) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第一个方法正在执行')
            resovled(1)
        },3000)
    })
    return promise;
}

function second(data) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第二个方法开始执行')
            resovled(2)
        },2000)
    })
    return promise;
}
function third(data) {
    let promise = new Promise((resovled,rejected) => {
        setTimeout(function() {
            console.log('第三个方法开始执行')
            resovled(3)
        },1000)
    })
    return promise;
}

Promise.race([first(),second(),third()])
.then(function(results){
    console.log('third先执行完毕')
})

结果如下:

image.png

可以看到当third最先执行完,执行完之后立即执行回调函数,同时,其它异步操作并未停止,而是继续执行输出结果。

总结

以上就是Promise的基本用法,这些方法也是实际项目中经常使用到的。除此之外还有done、finally、success、fail等方法,但这些并不在Promise标准中,是自己实现的语法糖。上述提到的进阶部分将在后续给出,真正掌握之后,相信大家对于Promise在其它的场景中的使用也会变得得心应手。