JavaScript|Promise使用详解

Promise 之前实现异步的弊端

首先看一个例子,再没有Promise之前,实现一个网络异步请求:

function requestData(url, successCallback, failtureCallback) {
    // 模拟网络请求
    setTimeout(() => {
        // 获取请求结果
        // url 传入的是 ricky 则请求成功,否则请求失败
        if (url === 'ricky') {
            let res = {
                name: 'ricky',
                age: 18
            };
            successCallback(res);
        } else {
            let error = '请求失败';
            failtureCallback(error);
        }
    }, 3000);
}

requestData('ricky', (res) => {
    console.log(res);
} , (error) => {
    console.log(error);
} );

这种实现方法有很多弊端:

  1. 如果是我们自己封装的requestData,那么我们在封装的时候必须要自己设计好callback名称, 并且使用好
  2. 如果我们使用的是别人封装的requestData或者一些第三方库, 那么我们必须去看别人的源码或者文档, 才知道它这个函数需要怎么去获取到结果

什么是 Promise

当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;

在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor

  • 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
  • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
  • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;

通过Promise改进的异步请求方法:

function requestData(url) {
    // 模拟网络请求
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // 获取请求结果
            // url 传入的是 ricky 则请求成功,否则请求失败
            if (url === 'ricky') {
                let res = {
                    name: 'ricky',
                    age: 18
                };
                resolve(res);
            } else {
                let error = '请求失败';
                reject(error);
            }
        }, 3000);
    });
}

requestData('ricky').then((res) => 
    console.log(res), (err) => 
    console.log(err)
);

Promise 三种状态

  • pending: 初始状态,表示Promise对象正在处理状态,还未完成
  • fulfilled: 表示Promise对象已经成功处理,当执行resolve方法时,Promise对象的状态就会变为fulfilled
  • rejected: 表示Promise对象已经处理失败,当执行reject方法时,Promise对象的状态就会变为rejected
const promise = new Promise((resolve, reject) => {
    // 调用resolve方法, then 传入回调执行
    resolve('successCallback')
    // 调用reject方法, catch 传入回调执行
    reject('errorCallback')
})

resolve 传参区别

  • 如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
  • 如果resolve传入一个Promise对象,那么这个Promise对象的状态就会被设置为当前Promise对象的状态;
  • 如果resolve传入一个有then方法的对象,那么这个对象的then方法就会被调用,并且传入的参数就是当前Promise对象的状态;
// 传入Promise对象
const newPromise = new Promise((resolve, reject) => {
  reject("err message")
})

new Promise((resolve, reject) => {
  // pending -> fulfilled
  resolve(newPromise)
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err) // err: err message
})

// 传入有then方法的对象
new Promise((resolve, reject) => {
  // pending -> fulfilled
  const obj = {
    then: function(resolve, reject) {
      // resolve("resolve message")
      reject("reject message")
    }
  }
  resolve(obj)
}).then(res => {
  console.log("res:", res)
}, err => {
  console.log("err:", err) // err: reject message
})

then 方法

then是Promise对象上的方法,在Promise原型上的Promise.prototype.then

两个参数

then方法接受两个参数,分别是resolve和reject,当Promise对象的状态变为fulfilled时,会执行resolve,当Promise对象的状态变为rejected时,会执行reject

promise.then(res => {
    console.log(res);
}, err => {
    console.log(err);
})

// 等价于
promise.then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

多次调用

Promise对象的then方法可以被多次调用,每次调用我们都可以传入对应的fulfilled回调;当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 1000);
})

promise.then(res1 => {
    console.log('res1:', res1);
})

promise.then(res2 => {
    console.log('res2:', res2);
})

promise.then(res3 => {
    console.log('res3:', res3);
})
// 上述三个结果都在同一时间打印

then方法的返回值

then方法的返回值是一个新的Promise对象,这个新的Promise对象的状态和原来的Promise对象保持一致。

// resolve情况
promise.then(res => {
    console.log('res:', res); // res: success
    return 'res success'
}).then(res => console.log(res), err => console.log(err)) // res success
// reject情况
promise.then(res => {
    console.log('res:', res); // res: success
    throw 'res err'
}).then(res => console.log(res), err => console.log(err)) // res err

catch 方法

catch方法是then方法的另一个版本,用于指定rejected的回调函数,当Promise对象的状态变为rejected时,会执行rejected

多次调用

Promise对象的catch也可以进行多次调用,每次调用都会执行对应的回调函数;当Promise的状态变成rejected的时候,这些回调函数都会被执行;

// 同样会在promise对象的状态变为rejected的时候同时执行
promise.catch(err => console.log("err1:" err))
promise.catch(err => console.log("err2:" err))

catch返回值

catch方法的返回值也是一个新的Promise对象,这个新的Promise对象的状态和原来的Promise对象保持一致。

promise.catch(err => {
    console.log('err:', err); // err: err message
    return err
}).then(res => {
    console.log('res:', res); // 执行此处
}).catch(err => {
    console.log(err);
})

promise.catch(err => {
    console.log('err:', err); // err: err message
    throw err
}).then(res => {
    console.log('res:', res);
}).catch(err => {
    console.log(err); // 执行此处
})

finally 方法

finally方法是then方法的另一个版本,用于指定无论Promise对象最后状态如何,都会执行的回调函数。finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。

promise.then(res => {
    console.log('res:', res); // res: success
}).catch(err => {
    console.log('err:', err);
}).finally(() => {
    console.log('finally'); // finally
})

resolve & reject 方法

有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve/Promise.reject 方法来完成。

Promise.resolve的用法相当于new Promise,并且执行resolve操作,可以传递除了普通值或对象、Promise和thenable对象作为参数。

Promise.reject的用法也相当于new Promise,并且执行reject操作,可以传递任何形态的参数,这些参数都会直接作为reject状态的参数传递到catch。

Promise.resolve('success') 

// 相当于

new Promise((resolve, reject) => {
    resolve('success')
})

Promise.reject('err')

// 相当于

new Promise((resolve, reject) => {
    reject('err')
})

Promise.all方法

Promise.all方法用于将多个Promise对象,包装成一个新的Promise对象。

新的Promise状态由包裹的所有Promise共同决定:

  • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
  • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
const r1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('r1 success')
    }, 1000);
})

const r2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('r2 success')
    }, 1500);
})

const r3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('r3 success')
    }, 2000);
})

const e1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('e1 error')
    }, 1000);
})

const e2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('e2 error')
    }, 2000);
})

Promise.all([r1, r2, r3]).then(res => {
    console.log(res); // [ 'r1 success', 'r2 success', 'r3 success' ]
})

Promise.all([r1, r2, r3, e1, e2]).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err); // e1 error
})

Promise.allSettled 方法

all方法有一个缺点:如果一个Promise变成reject状态,那么其他的Promise也会变成reject状态,并且不会再执行then方法。无法获取已经resolved状态的Promise。ES11中新增了allSettled方法,用于解决这个问题。该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,都有最终的状态,并且这个Promise的结果一定是fulfilled的。

返回的Promise的结果是一个数组,数组中的每一项都是一个Promise的结果,包含status状态以及对应的value值,数组的顺序与传入的Promise数组的顺序一致。

Promise.allSettled([r1, r2, r3, e1, e2]).then(res => {
    console.log(res); // [ { status: 'fulfilled', value: 'r1 success' }, { status: 'fulfilled', value: 'r2 success' }, { status: 'fulfilled', value: 'r3 success' }, { status: 'rejected', reason: 'e1 error' }, { status: 'rejected', reason: 'e2 error' } ]
})

Promise.race方法

如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:

Promise.race([r1, r2, r3]).then(res => {
    console.log(res); // r1 success
}) 

Promise.any方法

any方法是ES12中新增的方法,和race方法是类似的:

  • any方法会等到一个fulfilled状态,才会决定新Promise的状态
  • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态
Promise.any([r1, r2, r3, e1, e2]).then(res => {
    console.log(res); // 'r1 success'
})

Promise.any([e1, e2]).then(res => {
    console.log(res);
}) 
// 报错:Error: All promises were rejected