ES6 Promise的基本使用

366 阅读9分钟

什么是 Promise

Promise 是异步编程的一种解决方案,比传统的解决方案:回调函数和事件,更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象

Promise 对象两个特点:

  • 1、对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:

    • pending:(进行中)初始状态,没有变为成功或失败
    • fulfilled:(已成功)意味着操作成功完成
    • rejected:(已失败)意味着操作失败

    一个新创建的 Promise 处于 pending 状态,当调用 resolve 或 reject 函数后,Promise 处于 fulfilled 或 rejected 状态,此后 Promise 的状态保持不变 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 的英语意思「承诺」的由来,表示其他手段无法改变

  • 2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变只有两种可能:从 pending 变为 fulfilled 或从 pending 变为 rejected。只要这两种情况发生,状态就不会再改变,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的

Promise 缺点:

  • 无法取消 Promise,一旦新建它就会立即执行,中途无法取消
  • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
  • 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

Promise.resolve() 和 Promise.reject() 方法

使用 new 来调用 Promise 的构造函数创建 Promise 实例(Promise 实例对象被创建后就会立即执行),该构造函数会把一个叫做“处理器函数”(executor function)的函数作参数。这个“处理器函数”接受两个函数参数: resolvereject 。当异步任务顺利完成且返回结果值时,会调用 resolve 函数,当异步任务失败且返回失败原因(通常是一个错误对象)时,会调用 reject 函数

let p = new Promise(function(resolve, reject) {
    //执行一些异步任务
    if(/* 异步操作成功 */) {
    	resolve(res)   //调用resolve函数,pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
    }else {
    	reject(err)   //调用reject函数,pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
	}
})

Promise.prototype.then() 链式调用

then() 方法为 Promise 实例添加状态改变时的回调函数,该方法返回一个新的 Promise 实例,因此可以采用 then() 方法后面再调用另一个 then() 方法的链式写法 该方法可以接受两个回调函数作为参数:

  • 第一个参数是 fulfilled 状态的回调函数
  • 第二个参数(可选)是 rejected 状态的回调函数(两个函数都接受 Promise 对象传出的值作为参数)

then() 方法链式调用时,前一个 then() 方法指定的回调函数,返回一个新的 Promise 实例,后一个 then() 方法指定的回调函数,会等待这个新的 Promise 实例状态发生变化后,调用对应状态的回调函数

let p = new Promise(function(resolve, reject) {
    //执行一些异步任务
    if(/* 异步操作成功 */) {
    	resolve(res)   //调用resolve函数,pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
    }else {
    	reject(err)   //调用reject函数,pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
	}  
})
p.then(function(res) {
	console.log(res)   //fulfilled状态的回调函数,接收resolve函数的值作为参数
}, function(err) {
	console.log(err)   //rejected状态的回调函数,接收reject函数的值作为参数
})

Promise.prototype.catch() 捕获异常

catch() 方法用于指定发生错误时的回调函数。如果 Promise 实例中异步操作抛出错误,状态变为 rejected,就会被 catch() 方法捕获到这个错误(在 resolve 函数后面抛出错误,不会被捕获到,此时 Promise 对象的状态已经改变为 fulfilled,不会再改变),调用 catch() 方法指定的回调函数做错误的处理。如果 then() 方法指定的回调函数,在运行中抛出错误,也会被 catch() 方法捕获到。 该方法是 then(null, rejection) 或 then(undefined, rejection) 的别名(建议使用 catch() 方法,而不使用 then() 方法的第二个回调函数参数,因为 catch() 方法可以捕获到 then() 方法运行中抛出的错误)

let p = new Promise(function(resolve, reject) {
    //执行一些异步任务,pending状态改变时执行对应的函数
    if(/* 异步操作成功 */) {
    	resolve(res)   //pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
    }else {
    	reject(err)   //pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
	}   
})
p.then(function(res) {
	console.log(res)   //fulfilled状态的回调函数,接收resolve函数的值作为参数
}).catch(function(err) {
	console.log(err)   //rejected状态的回调函数,接收reject函数的值作为参数(或者then()方法在运行中抛出的错误)
})

Promise.prototype.finally()

finally() 方法返回一个 Promise,在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。 这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式,也避免了同样的语句需要在 then() 和 catch() 中各写一次的情况

let p = new Promise(function(resolve, reject) {
    //执行一些异步任务,pending状态改变时执行对应的函数
    if(/* 异步操作成功 */) {
    	resolve(res)   //pending状态 -> fulfilled状态,将异步操作的结果,作为参数传递出去
    }else {
    	reject(err)   //pending状态 -> rejected状态,将异步操作报出的错误,作为参数传递出去
	}   
})
p.then(function(res) {
	console.log(res)   //fulfilled状态的回调函数,接收resolve函数的值作为参数
}).catch(function(err) {
	console.log(err)   //rejected状态的回调函数,接收reject函数的值作为参数(或者then()方法在运行中抛出的错误)
}).finally(function() {
    //promise结束时,执行的回调函数(返回状态为fulfilled或rejected)
})

解决回调地狱的问题

JavaScript 本身是单线程的,为了解决一些单线程带来的问题,异步编程成为了 JavaScript 中非常重要的一部分。当我们使用多次异步调用,异步调用的结果如果存在依赖,则会形成回调函数的嵌套,当回调函数嵌套层级过多时,就形成了回调地狱问题,回调地狱会使代码变得可读性差、难以理解和维护

let num = 1
let addNum = function(callback) {
    setTimeout(function(){
        num++
		callback()
	}, 1000)
}
//回调函数嵌套层级过多时,就形成了回调地狱问题,使代码变得可读性差、难以理解和维护
addNum(function() {
    addNum(function() {
        addNum(function() {
            addNum(function() {
                console.log(num)   //5
            })
        })
    })
})

Promise 很好的解决了回调地狱的问题,提高代码的可读性和可维护性。Promise 采用链式的 then(),可以指定一组按照次序调用的回调函数,前一个回调函数返回一个 Promise 对象(即有异步操作),后一个回调函数就会等待该 Promise 对象的状态发生变化,才会被调用 前一个回调函数完成后,会将返回结果作为参数,传入第二个回调函数

let num = 1
let addNum = function(num) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
            num++
            resolve(num)
        }, 1000)
    })
}
//Promise采用链式的then()解决回调地狱问题,提高了代码的可读性和可维护性
addNum(num).then(function(res) {
    return addNum(res)
}).then(function(res) {
    return addNum(res)
}).then(function(res) {
    return addNum(res)
}).then(function(res) {
    console.log(res)   //5
}).catch(function(err) {
    console.log(err)
})

Promise.all()

Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。该方法接受一个数组作为参数,数组的每一项都应为 Promise 实例,如果不是,就会先调用 resolve() 方法,将参数转为 Promise 实例,再进一步处理(该方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例) Promise.all() 方法包装后的新 Promise 实例的状态由数组参数的每一项决定:

  • 只有作为参数的 Promise 实例的状态都变成 fulfilled,包装后的新 Promise 实例的状态才会变成 fulfilled,此时所有作为参数的 Promise 实例的返回值组成一个数组,传递给包装后的新 Promise 实例的回调函数

    let p1 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(1)
        }, 1000)
    })
    let p2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(2)
        }, 2000)
    })
    //作为参数的Promise实例的状态都变成fulfilled
    //then()接受到作为参数的Promise实例的返回值的数组
    Promise.all([p1, p2]).then(function(res) {
        console.log(res)   //[1, 2]
    }).catch(function(err) {
        console.log(err)
    })
    
  • 只要作为参数的 Promise 实例中有一个状态变为 rejected,包装后的新 Promise 实例的状态就会变成 rejected,此时第一个状态变为 rejected 的实例的返回值,会传递给包装后的新 Promise 实例的回调函数(注意:如果作为参数的 Promise 实例,自己定义了catch() 方法,一旦它变为 rejected 状态,并不会被 Promise.all() 的 catch() 方法捕获)

    let p1 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(1)
        }, 1000)
    })
    let p2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject("error")
        }, 2000)
    })
    //作为参数的Promise实例中有一个状态变为rejected
    //catch()捕获到第一个状态变为rejected的实例的返回值
    Promise.all([p1, p2]).then(function(res) {
        console.log(res)  
    }).catch(function(err) {
        console.log(err)   //"error"
    })
    
总结:全部成功,得到成功的数组;只要有一个失败,得到第一个失败

Promise.race()

Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。该方法和 Promise.all() 方法一样,接受一个数组作为参数,数组的每一项都应为 Promise 实例,如果不是,就会先调用 resolve() 方法,将参数转为 Promise 实例,再进一步处理 Promise.race() 方法包装后的新 Promise 实例的状态:

  • 只要作为参数的 Promise 实例中有一个实例率先改变状态,包装后的新 Promise 实例的状态就跟着改变,那个率先改变的 Promise 实例的返回值,会传递给包装后的新 Promise 实例的回调函数

    let p1 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(1)
        }, 1000)
    })
    let p2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject("error")
        }, 2000)
    })
    //作为参数的Promise实例中有一个实例率先改变状态为fulfilled
    //then()接受到那个率先改变状态为fulfilled的Promise实例的返回值
    Promise.race([p1, p2]).then(function(res) {
        console.log(res)   //1
    }).catch(function(err) {
        console.log(err)
    })
    
    let p1 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject("error")
        }, 1000)
    })
    let p2 = new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(2)
        }, 2000)
    })
    //作为参数的Promise实例中有一个实例率先改变状态为rejected
    //catch()捕获到那个率先改变状态为rejected的Promise实例的返回值
    Promise.race([p1, p2]).then(function(res) {
        console.log(res)  
    }).catch(function(err) {
        console.log(err)   //"error"  
    })
    
总结:得到最先结束的状态,不管是成功还是失败