在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
Promise 是异步编程的一种解决方案: 从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。 promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态);状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
作用:
- promise是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外)
- 并未剥夺函数return的能力,因此无需层层传递callback,进行回调获取数据
- 代码风格,容易理解,便于维护
- 多个异步等待合并便于解决
三种状态
- pending[等待]初始状态
- resolved(又称 fulfilled)[成功]操作成功
- rejected[失败]操作失败
当promise状态发生改变,就会触发then()里的响应函数处理后续步骤;
promise状态一经改变,不会再变。
基本的 api
-
Promise.resolve()
-
Promise.reject()
-
Promise.prototype.then()
-
Promise.prototype.catch()
-
Promise.all() // 所有的完成
-
Promise.race() // 竞速,完成一个即可
一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:
new Promise(function (resolve, reject) {
let timeOut = Math.random() * 2;
setTimeout(function () {
if (timeOut < 1) {
console.log('call resolve()...');
resolve('200 OK');
}
else {
console.log('call reject()...');
reject('timeout in ' + timeOut + ' seconds.');
}
}, timeOut * 1000);
}).then(function (r) {
console.log('Done: ' + r);
}).catch(function (reason) {
console.log('Failed: ' + reason);
});
Promise.All()
Promise.all() 方法返回一个 Promise 实例,Promise.all()可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = new Promise((resolve, reject) => {
reject('失败了')
})
let p4 = new Promise((resolve, reject) => {
reject('failed')
})
Promise.all([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
Promise.all([p1,p3,p2,p4]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
// Array(2) ["成功了", "success"]
// 失败了
PS:Promise.all()获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
Promise.race()
Promse.race()就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
// failed
几道面试题
题目一:
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
//1 2 4 3
Promise 构造函数是同步执行的,promise.then
中的函数是异步执行的。
题目二:
const promise = new Promise((resolve, reject) => {
resolve('success1')
reject('error')
resolve('success2')
})
promise
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
// then: success1
构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,呼应代码二结论:promise 状态一旦改变则不能再变。
题目三:
Promise.resolve(1)
.then((res) => {
console.log(res)
return 2
})
.catch((err) => {
return 3
})
.then((res) => {
console.log(res)
})
//1 2
promise 链式调用。提起链式调用我们通常会想到通过 return this
实现,不过 Promise 并不是这样实现的。promise 每次调用 .then
或者 .catch
都会返回一个新的 promise,从而实现了链式调用。
题目四:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
}, 1000)
})
const start = Date.now()
promise.then((res) => {
console.log(res, Date.now() - start)
})
promise.then((res) => {
console.log(res, Date.now() - start)
})
// once
// success 1026
// success 1027
promise 的 .then
或者 .catch
可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then
或者 .catch
都会直接拿到该值。
题目五:
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(console.error)
//TypeError: Chaining cycle detected for promise #<Promise>
.then
或 .catch
返回的值不能是 promise 本身,否则会造成死循环。
题目六:
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
// 1
.then
或者 .catch
的参数期望是函数,传入非函数则会发生值穿透。
题目七:
process.nextTick(() => {
console.log('nextTick')
})
Promise.resolve()
.then(() => {
console.log('then')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
// end
// nextTick
// then
// setImmediate
process.nextTick
和 promise.then
都属于 microtask,而 setImmediate
属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。