本文主要介绍了个人对promise的一些粗浅理解,如有不正确的地方,还请指正。
一. Promise是什么
从JS语法层面上讲,Promise是一个函数,可以通过new关键字进行调用然后生成一个对象,这个对象被成为promise对象。promise对象有三种状态,为pedding、resolved、rejected,这是promise的重要机制。 从功能的角度来讲,Promise是一个容器,它里面包含着一个未来才会得到的(通常是异步)结果。 Promise帮助调用者拿回了调用回调函数的主动权,而不是把主动权交给第三方函数。
二. promise解决了什么问题
2.1 回调的涉及的信任问题
1. 使用回调风格的异步处理
以下关于callback函数的执行有几种信任问题。
function getData(url, callback) {
let req = new XMLHttpRequest()
req.open('GET', url, true)
req.onload = function () {
if (req.status === 200) {
// 使用回调处理会存在几种潜在的问题
// 1. 过早或者过晚调用
// 2. 干脆不调用
// 3. 调用了很多次
// 4. 没有给回调函数传入指定的参数
callback(req.responseText)
} else {
console.log(new Error(req.statusText))
}
}
req.onerror = function () {
console.log(new Error(req.statusText))
}
req.send()
}
function callback(text) {
console.log(text)
}
let url = 'http://api.myjson.com/bins/16hvos'
getData(url, callback)
2. 使用promise的异步处理
function getData(url) {
return new Promise(function (resolve, reject) {
let req = new XMLHttpRequest()
req.open('GET', url, true)
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText)
} else {
reject(new Error(req.statusText))
}
}
req.onerror = function () {
reject(new Error(req.statusText))
}
req.send()
})
}
let url = 'http://api.myjson.com/bins/16hvos'
getData(url).then(function onFullfilled(value) {
console.log(value)
}).catch(function onRejected(value) {
console.log(value)
})
2.2 回调地狱
回调地狱会导致代码晦涩难懂,不易维护,错误处理复杂。
1. 回调地狱(callback hell)示例代码
// 先执行taskA 得到符合预期结果; 执行taskB, 得到预期结果,执行taskC, 得到预期结果,接着处理。。。
function taskA(callback) {
let result = 1
setTimeout(() => {
callback(result)
}, 2000)
}
function taskB(callback) {
let result = 2
setTimeout(() => {
callback(result)
}, 2000)
}
function taskC(callback) {
let result = 3
setTimeout(() => {
callback(result)
}, 2000)
}
taskA(function (res) {
if (res === 1) {
taskB(function (res) {
if (res === 2) {
taskC(function (res) {
if (res === 3) {
console.log(res)
}
})
}
})
}
})
可以看出每执行一个函数都需要给其一个回调函数,而且代码末尾有很多括号的嵌套,虽然可以使用一定方法可以改善这种嵌套的方法,但是依然代码依然不够易读。比如采用如下方法抽离一部分代码
function a(res) {
if (res === 1) {
taskB(b)
}
}
function b (res) {
if (res === 2) {
taskC(c)
}
}
function c(res) {
if (res === 3) {
console.log(res)
}
}
taskA(a) // 3
即使采用以上方式处理,阅读此代码仍然让人头大。
2. 使用promise处理回调地狱
// taskA执行,拿到预期结果,返回一个resolved的promise对象
function taskA() {
let result = 1
return new Promise(function (resolve, reject) {
setTimeout(() => {
if (result === 1) {
resolve(result)
} else {
reject(new Error('fail'))
}
}, 2000)
})
}
// 一个resolved状态的promise对象可以在其then方法中拿到异步处理的结果
// 在then方法中的回调函数中使用return会新生成一个resolved状态的promise对象
taskA().then(function (result) {
return result
}).then(function taskB() {
let result = 2
return new Promise(resolve => {
setTimeout(() => {
resolve(result)
}, 2000)
})
}).then(function taskC() {
let result = 3
return new Promise(resolve => {
setTimeout(() => {
resolve(result)
}, 2000)
})
}).then(function (result) {
console.log(result)
})
三. promise的使用
3.1 创建一个promise对象
1. 构造函数形式
Promise函数接受一个函数作为参数,这个函数有两个参数,resolve和reject, 用来改变所生成的promise对象的状态。这个函数参数会立即执行。
let promise = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve(42)
}, 2000)
})
promise.then(function (value) {
console.log(value) // 42
})
Object.getPrototypeOf(promise) === Promise.prototype // true
2. Promise.resolve
Promise.resolve() 直接返回一个已解析状态的promise对象。
var promise = Promise.resolve(42)
// 相当于
var promise = new Promise(function (resolve, reject) {
resolve(42)
})
3. Promise.reject
Promise.reject 直接返回一个已拒绝状态的promise对象。
var promise = Promise.reject(new Error('ok'))
// 相当于
var promise = new Promise(function (resolve, reject) {
reject(new Error('fail'))
})
3.2 promise#then 和 promise#catch
1. promise#then
(1) 每一个promise对象都有then方法,当这个promise对象的状态变为resolved时,会执行then中注册的回调函数。
let promise = Promise.resolve(42)
promise.then(function (value) {
console.log(value) //42
})
(2) promise对象的then方法会返回一个新的promise对象,因此我们可以在新生成的promise对象中继续使用then方法。
let promise = Promise.resolve(42)
promise.then(function (value) {
console.log(value)
}).then(function (value) {
console.log('ok')
})
根据then方法的特性可以实现promise对象的链式调用。
let promise = Promise.resolve()
function taskA() {
console.log('taskA')
}
function taskB() {
console.log('taskB')
}
function onRejected(error) {
console.log(error)
}
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
// taskA taskB
以上例子中,实现了promise的链式调用。当taskA和taskB中没有抛出错误时,程序会一直执行下去,其中catch是为了捕获taskA或taskB出现的异常。如果taskA或者taskB中出现了错误,那么catch中的回调函数就会执行。如下:
let promise = Promise.resolve()
function taskA() {
throw new Error('fail')
}
function taskB() {
console.log('taskB')
}
function onRejected(error) {
console.log(error)
}
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
// Error fail
如果taskA出现了一个错误,那么taskB将不会执行,会执行调用catch中的回调函数。 3). 链式调用传值 想要实现链式调用的传值,只需要在then中的回调函数给出返回值,Promise就会自动将这个返回值解析为一个promise对象。
let promise = Promise.resolve(10)
function taskA(value) {
return value + 10
}
function taskB(value) {
return value + 10
}
function taskC(value) {
console.log(value)
}
promise
.then(taskA)
.then(taskB)
.then(taskC) // 30
2. promise#catch
当一个promise的状态为rejected时,那么它的catch方法中的回调函数将会被执行。catch方法其实就是then方法中第二个回调函数。
let promise = Promise.reject('fail')
promise.catch(function (error) {
console.log(error) // fail
})
// 相当于
promise.then(undefined, function (error) {
console.log(error) // fail
})
catch也会返回一个promise对象。
3.3 Promise.all 和 Promise.race
1. Promise.all
Promise.all接受一个由promise对象组成的数组参数,当每个promise的状态都变成resoved或rejected时,Promise.all返回的promise对象的状态才会改变(resolved或者rejected)。
// getData函数返回一个promise, 并且函数体内的Promise函数会立即执行,即立即执行函数体内的异步任务
function getData(url) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest()
req.open('GET', url, true)
req.onload = function () {
if (req.status === 200) {
return req.responseText
} else {
return new Error(req.statusText)
}
}
req.onerror = function () {
reject(new Error(req.statusText))
}
req.send()
})
}
// 错误写法
// getData函数前加了async关键字,因此它也返回一个promise对象,但是函数体内的请求时异步的,
// getData会立即执行,然后再去执行异步任务,因此getData返回的promise永远是resolved undefined
async function getData(url) {
var req = new XMLHttpRequest()
req.send()
req.open('GET', url, true)
req.onload = function () {
if (req.status === 200) {
console.log('ok')
return req.responseText
} else {
return new Error(req.statusText)
}
}
req.onerror = function () {
reject(new Error(req.statusText))
}
}
Promise.all([
getData('http://azu.github.io/promises-book/json/comment.json'),
getData('http://azu.github.io/promises-book/json/people.json')
]).then(res => {
console.log(res)
})
2. Promise.race
Promise.race也接受一个由promise对象组成的数组参数,当其中的一个promise对象的状态变为resolved或是rejected时,Promise.race返回的promise对象才会改变(resolved或者rejected)。
3.4 只执行异步操作的promise 和 async/await
1. 一个立即变为resolved状态的promise对象,promise.then中注册的回调函数还是会被以异步方式调用。
function asyncPromise () {
return new Promise(resolve => {
resolve(3)
})
}
function test() {
asyncPromise().then(function (value) {
console.log(value)
})
console.log(1)
}
test()
console.log(2)
// 输出顺序为 1 2 3
2. 一个function前加了async关键字,那么这个函数会返回一个promise对象
可以把async当做是一个返回值为promise对象的函数的语法糖。
async function test() {
return 1
}
test() // Promise {<resolved>: 1} google浏览器的打印结果
// 相当于
function test() {
return Promise.resolve(1)
}
test() // Promise {<resolved>: 1} google浏览器的打印结果
3. await 关键字后面如果是一个promise对象,那么它会把这个promise对象解析后的结果取出来,并且其后面的不管是同步代码还是异步代码都会等到这个promise对象解析出来后(由pedding变为resolved或rejected),才会执行后面的代码。
await 让异步代码变为了同步(同步执行)。
function asyncFunction () {
return new Promise(resolve => {
resolve(2)
})
}
async function test() {
let value = await asyncFunction()
console.log(value)
console.log(3)
}
test()
console.log(1)
// 打印结果为 1 2 3
4. 项目实践
采用两种方式模拟一个经过vuex获取数据的过程。 (1) 直接返回promise对象的方式
// src/People.vue
created () {
this.$store.dispatch('GetPeople', { page: 1, size: 20 }).then(response => {
console.log(response) // [{ id: 1, name: 'Joe'}]
})
}
// src/store/people/actions.js
GetPeople({ commit }, payload) {
return new Promise(function (resolve, reject) {
axios.get('http://www.example.com/people', { payload })
.then(function (response) {
resolve(response) // response = [{ id: 1, name: 'Joe'}]
}).catch(function (error) {
reject(error)
})
})
}
(2) async / await
// src/People.vue
async created() {
await this.$store.dispatch('GetPeople', { page: 1, size: 20 })
// [{ id: 1, name: 'Joe'}]
}
// src/store/people/actions.js
async GetPeople({ commit }, payload) {
const response = await axios.get('http://www.example.com/people', { payload })
return response
}
参考:

<关于我们>
我们是来自帝都的一枚前端程序猿 + 一枚前端程序媛。
这里发布的文章是我们对学习内容的总结,预计会每周至少会更新一篇。
目前我们学习计划是: 小程序实战 => vue 进阶用法 => vue 原理 => css 基础 => es6 => js 深入
另外,工作中用到的一些技术和完成的功能,我们也会及时总结更新在这里
如文章有错误或表述不清晰,欢迎各位留言反馈~~