前言
理解什么是Promise,以及Promise有哪些作用毫无疑问是我们学习JavaScript这门语言的必经之路。
Promise是什么?
Promise是异步编程的一种解决方案,从语法上来将,它是一个对象, 代表着一个异步操作最终完成或失败,从语意上来讲,它是承诺,承诺过一段时间给你一个结果。promise有三钟状态,分别是pending,fulfiled,rejected。
由于它的原型存在then,catch,finally会返回一个新的promise所以可以允许我们链式调用,解决了传统的回调地狱的问题。
由于它本身存在all方法,所以可以支持多个并发请求,获取并发请求中数据。
Promise特性
- promise的状态一经改变就不能再改变。
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的值都会被包裹成promise对象,例如return 2会被包装为return Promise.resolve(2)。
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
结果:
1
2
return 2会被包装成resolve(2)
then,catch方法
.then与.catch的不同点
1.then用于执行请求成功的回调函数 .then 方法可以接收两个参数,第一个就是处理成功的函数,第二个是处理失败的函数,在某种程度上可以认为.then的第二个参数就是.catch的简便写法。
Promise.reject('err!!!')
.then((res) => {
console.log('success', res)
}, (err) => {
console.log('error', err)
}).catch(err => {
console.log('catch', err)
})
这里的执行结果是:
'error' 'error!!!'
它进入的是then()中的第二个参数里面,而如果把第二个参数去掉,就进入了catch()中
Promise.reject('error!!!')
.then((res) => {
console.log('success', res)
}).catch(err => {
console.log('catch', err)
})
执行结果:
catch' 'error!!!'
如果是下面这样的话
Promise.resolve()
.then(function success (res) {
throw new Error('error!!!')
}, function fail1 (err) {
console.log('fail1', err)
}).catch(function fail2 (err) {
console.log('fail2', err)
})
由于Promise调用的是resolve(),因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获。
因此执行结果为:
fail2 Error: error!!!
2.catch用于执行请求失败的回调函数 catch不管连接在哪里,都能捕获上层不能捕获的错误。
const promise = new Promise((resolve, reject) => {
reject("error");
resolve("success2");
});
promise
.then(res => {
console.log("then1: ", res);
}).then(res => {
console.log("then2: ", res);
}).catch(err => {
console.log("catch: ", err);
}).then(res => {
console.log("then3: ", res);
})
结果:
"catch: " "error"
"then3: " undefined
catch不管被连接到哪里,都能捕获上层未捕捉过的错误。
至于then3也会被执行,那是因为catch()也会返回一个Promise,且由于这个Promise没有返回值,所以打印出来的是undefined。
.catch和.then的相同点
1.catch与.then都都会返回一个新的promise
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
结果
'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
2.then与.catch 方法return一个error对象并不会抛出错误,所以不会被后面的catch捕获
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
猜猜这里的结果输出的是什么 🤔️ ?
你可能想到的是进入.catch然后被捕获了错误。
结果并不是这样的,它走的是.then里面
"then: " "Error: error!!!"
因为返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))。
当然如果你抛出一个错误的话,可以用下面👇两的任意一种:
return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')
3.then与.catch返回的不能是调用它们的promise本身,否则会造成死循环
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
Uncaught (in promise) TypeError: Chaining cycle detected for promise #
4.then与.catch的参数指望是函数,传入非函数会发生值透传
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1) 的值直接传到最后一个then里。
所以输出结果为:
1
5.promise的.then和.catch可以被调用多次,但是如果Promise的状态发生了变化,并且有了一个值,那么后续每次调用.then与.catch都会直接拿到该值。
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('timer')
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)
})
执行结果:
'timer'
'success' 1001
'success' 1002
当然,如果你足够快的话,也可能两个都是1001。
Promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。
.finally方法
- .finally方法不管promise对象最后的状态是成功还是失败如何都会执行。
Promise.resolve('1')
.then(res => {
console.log(res)
})
.finally(() => {
console.log('finally')
})
结果
`'1'`
`'finally'`
- .finally方法不接受任何参数,因此也无法通过finally方法得知promise的状态
- finally最终返回的默认会是上一个Promise的对象值,不过如果抛出的是一个异常则返回异常的peomise对象
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then(res => {
console.log('finally2后面的then函数', res)
})
结果
'finally2'
'finally2后面的then函数' '2'
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中抛出的异常')
})
.then(res => {
console.log('finally后面的then函数', res)
})
.catch(err => {
console.log('捕获错误', err)
})
执行结果为:
'finally1'
'捕获错误' Error: 我是finally中抛出的异常
但是如果改为return new Error('我是finally中抛出的异常'),打印出来的就是'finally后面的then函数 1'
.all, .race方法
.all接收一组异步任务,然后并行执行异步任务,并且在所有的异步任务执行完成后才执行回调。
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log(res))
结果
1
2
3
[1, 2, 3]
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。
.all()后面的.then()里的回调函数接收的就是所有异步操作的结果。而且这个结果中数组的顺序和Promise.all()接收到的数组顺序一致!!!
有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
.race也是执行一组异步任务,然后并行执行异步任务,只保留第一个执行完成的一步操作,其他方法仍在执行,不过执行结果会被抛弃
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result: ', res))
.catch(err => console.log(err))
结果
1
'result: ' 1
2
3
这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作
all和.race传入的数组如果有会抛出异常的异步任务,那么只有最先抛出的任务会被捕获。并且是被then的第二个参数或者.catch捕获,并且不会影响数组中其他异步任务的执行
手写简易Promise
1.promise是个类
2.promise,在执行这个类时会传入一个执行器,这个执行器会立即执行
3.Promise有三种状态
4.状态只能有pending转为fulfiled或pending转为rejected,一旦状态发生变化,便不可二次修改
5.then方法内部做状态判断,成功状态调用成功回调函数,失败状态调用失败回调函数
6.为了实现异步逻辑,在then方法中可以进行判断,如果时pending状态的promise,可以先将其回调函数存起来,在状态发生变化时调用
7.为了实现链式调用,可以在then方法返回一个promise对象,并且在执行回调函数时获取返回值x,实现一个函数判断返回值类型,如果时下x === promise便抛出类型错误,如果x是promise的子类型,便返回x.then,如果是普通值,便直接resolve。
8.错误捕获.使用trycatch进行错误捕获
9.then参数可选,判断传入的是不是函数,如果是返回函数,如果不是,返回普通值
10.实现静态方法resolve和reject
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 存储成功回调函数
onFulfilledCallbacks = [];
// 存储失败回调函数
onRejectedCallbacks = [];
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
})
return promise2;
}
// resolve 静态方法
static resolve (parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter;
}
// 转成常规方式
return new MyPromise(resolve => {
resolve(parameter);
});
}
// reject 静态方法
static reject (reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 判断x是不是 MyPromise 实例对象
if(x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else{
// 普通值
resolve(x)
}
}
module.exports = MyPromise;
总结
-
Promise的状态一经改变就不能再改变。(见3.1) -
.then和.catch都会返回一个新的Promise。(上面的👆1.4证明了) -
catch不管被连接到哪里,都能捕获上层未捕捉过的错误。(见3.2) -
在
Promise中,返回任意一个非promise的值都会被包裹成promise对象,例如return 2会被包装为return Promise.resolve(2)。 -
Promise的.then或者.catch可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。(见3.5) -
.then或者.catch中return一个error对象并不会抛出错误,所以不会被后续的.catch捕获。(见3.6) -
.then或.catch返回的值不能是 promise 本身,否则会造成死循环。(见3.7) -
.then或者.catch的参数期望是函数,传入非函数则会发生值透传。(见3.8) -
.then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法。(见3.9) -
.finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。 -
Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。 -
.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。 -
Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。 -
all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。