1.含义
Promise是异步编程的一种解决方案,在es6中它被写进语言标准,统一了用法,原生提供了Promise对象。
通俗地说,Promise像一个容器,保存着某个未来结束的事件(异步事件)的结果。
而从语法上说,Promise是一个对象,可以从中获得异步操作的信息,通过各种API对异步方法进行处理。
总之,有了Promise对象,就可以将异步操作以同步操作的流程表达出来。
异步方法不会立即返回最终的值,而是先返回一个promise,以便未来把值交给使用者。
2.基本特点
Promise对象具有以下两个特点:
(1)对象的状态不受外界影响
Promise对象代表一个异步操作,有三种状态:
- pending 初始状态
- fulfilled 操作成功
- rejected 操作失败
只有异步操作的结果,可以决定当前的状态。 任何其它操作都无法改变这个状态。
(2)状态一旦改变后,就不会再次发生改变
状态改变只有两种可能:
- pending -> fullfilled
- pending -> rejected
改变发生后,状态就凝固了,此时的状态称为:
- resolved
如果状态已经成为resolved,即使再对Promise对象添加回调函数,也会立即得到这个结果。
平时习惯用resolved状态指fullfilled状态,rejected状态仍然由rejected表示。
Promise也有一些缺点:
(1)一旦新建就会立即执行,无法中途取消
(2)如果不设置回调函数,Promise内部throw的错误不会反应到外部
(3)处于pending状态时,无法得知目前进展
3.基本用法
3.1 构造
es6中,Promise对象是一个构造函数,可以用来生成实例。
Promise构造函数以一个函数作为参数,这个函数的两个参数又是两个函数,分别是resolve和reject。
这两个函数由js提供,不用自己部署。
const promise = new Promise((resolve ,reject) =>{
//...some code
if(/*异步操作成功*/){
resolve(value);
}else{
reject(error);
}
})
一般使用resolve()方法指代状态从pending变成fullfilled,在异步操作成功时调用;
用reject()方法指代状态变为rejected,在异步操作失败时调用。
就像mdn中提到的:
We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
比如这个例子,如果图片加载成功,就调用resolve方法,否则调用reject方法:
function loadImageAsync(url) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function () {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
3.2 then/catch
promise实例生成后,可以通过then方法接受两个函数作为参数,分别指定resolved状态和rejected状态的回调函数。这两个函数都接受实例对象传出的值作为参数。
promise.then((value) =>{
//success
} , (error) =>{
//failure
})
例如这个基本的例子,体现出resolve()的传参和then方法内函数如何接收传过来的参数。
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(() =>{
//在异步事件执行成功时将resolve内参数传给then方法作为参数
resolve("Success!")
}, 250)
})
myFirstPromise.then((successMessage) => {
console.log("Yay! " + successMessage) //Yay! Success!
});
3.2.1 promise实例作为resolve()参数
通常来说,reject()的参数是Error对象的实例,也就是
reject(new Error(...))
而对于resolve()来说参数是很多样的,甚至可以将另一个Promise实例作为参数。即一个异步操作的结果是返回另一个异步操作。 例如下面这个例子:
const p1 = new Promise((resolve , reject) =>{
setTimeout(() =>{
// resolve('congrats , you success') , 3000
reject(new Error('sorry,you fail'))
} , 3000)
})
const p2 = new Promise((resolve , reject) =>{
setTimeout(() =>{
resolve(p1)
} , 1000)
})
p2.then(result => console.log(result))
.catch(error => console.log(error));
(1)若p1为pending,则p2需要等p1执行完才能更新自己状态
例子中p2状态在1s后就已经改变,然而resolve传入p1,因此p2自己的状态失效,由p1决定。同时p2后面的then语句也由p1决定。因此再过了2s后,p1变为rejected,触发catch方法指定的回调函数。
(2)若p1已经resolved或者rejected,那么p2会立刻执行
那么如果p2后触发呢,将p2中的setTimeout时间修改为
const p2 = new Promise((resolve , reject) =>{
setTimeout(() =>{
resolve(p1)
} , 6000)
})
那么3s一到就会变成p1先改变状态,但是由于p1没有相应的catch(),会导致3s-6s时捕获不到错误。
然后到6s时p2状态改变,立即执行catch方法,输出捕获到的报错信息:
3.2.2 then的链式调用
比如第一个then方法的回调函数返回一个Promise对象,这时第二个then方法指定的回调函数就要等待这个Promise对象状态变化来决定自己是调用resolve对应的回调函数还是rejected对应的回调函数。
getJSON("/post/1.json")
.then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved:", comments),
).catch(
err => console.log("rejected:" , err)
)
3.2.3 catch指定回调函数捕获错误
catch的用法上面已经学过了,注意一下不同的写法就行
const promise = new Promise((resolve , reject) =>{
throw new Error('test');
//等效于reject(new Error('test'))
})
.catch(error => console.log(error))
如果Promise状态已经变成resolved,那么再抛出错误是无效的。
const promise = new Promise((resolve , reject) =>{
resolve('ok')
reject(new Error('test'))
})
.then(msg => console.log(msg))
.catch(error => console.log(error))
然而即使没有catch,promise内部的错误也不会影响外部代码的执行
const someAsyncThing = function () {
return new Promise(function (resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
}
someAsyncThing().then(() => console.log('resolved'))
console.log('123')
//123
// Uncaught (in promise) ReferenceError: x is not defined
同样,catch也是可以链式调用的
someAsyncThing().then(()=>{
return someOtherAsyncThing();
}).catch(error=> {
console.log('oh no', error);
// 下面一行会报错,因为y没有声明
y + 2;
}).catch(error =>{
console.log('carry on', error);
});
4.常用API
4.1 Promise.prototype.finally()
4.1.1 用法
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况
比如这个例子,不管发生了什么,都会打印'finally'
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
//resolve('success')
reject(new Error('fail'))
}, 1000)
})
.finally(() => {
console.log('finally')
})
.then(
msg => console.log(msg),
err => {throw err}
)
//finally
//Uncaught (in promise) Error: fail
4.1.2 手写
其实finally就是把本来要写的then和catch合起来了而已,相当于不管怎样都执行callback(),重写的逻辑是比较清晰的:
- 无论如何执行callback()
- 用Promise.resolve()将callback()执行结果转化为Promise对象,这样才能加上then方法,将接收到的msg/err继续传到下一层then中
Promise.prototype.myFinally = function(callback){
return this.then(val =>{
// 把上层传过来的val return出来传下去
return Promise.resolve(callback()).then(() => val);
} , err =>{
// 把上层传过来的err throw出来传下去
return Promise.resolve(callback()).then(() => {throw err})
})
}
简洁版
Promise.prototype.myFinally = function(callback) {
return this.then(
val => Promise.resolve(callback()).then(() => val),
err => Promise.resolve(callback()).then(() => {throw err})
)
}
此处为什么要在callback()后面return一个值而不是这么写只执行callback()就完事呢
msg => Promise.resolve(callback()),
err => Promise.resolve(callback())
比如3.1.1中的例子,finally()下方还有个then方法接收finally传递下来的参数然后打印,因此在callback()后面加上then方法用来把finally从上面接收到的resolve/reject传过来的参数继续往下传。
4.1.3 返回值
finally方法总是会返回原来的值
// resolved 的值是 undefined
console.log(Promise.resolve(2).then(() => {}, () => {}))
// resolved 的值是 2
console.log(Promise.resolve(2).finally(() => {}))
// rejected 的值是 undefined
console.log(Promise.reject(3).then(() => {}, () => {}))
// rejected 的值是 3
console.log(Promise.reject(3).finally(() => {}))
4.2 Promise.resolve()
4.2.1 用法
Promise.resolve()可以将现有对象转为Promise对象
let p = Promise.resolve(123);
console.log(p); //Promise {<fulfilled>: 123}
p.then(val => console.log(val)); //123
Promise.resolve()等价于上文中的构造Promise后在其中写resolve()
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
4.2.2 四种参数
Promise.resolve()方法的参数分成四种情况。
(1) 参数本身就是一个Promise实例
原封不动地返回这个实例
(2) 参数是一个具有then方法的对象
比如这个例子
let thenableObj = {
then : (resolve , reject) =>{
resolve(123);
}
}
let p = Promise.resolve(thenableObj);
p.then(val => console.log(val)); //123
Promise.resolve()会将这个对象转化为Promise对象,然后执行对象中的then方法。
(3) 参数是普通对象/不是对象
如果参数是原始类型或者是不带then方法的普通对象,那么Promise.resolve()返回一个新的Promise对象,状态为resolved
let p = Promise.resolve('yoimiya');
p.then(val => console.log(val)); //yoimiya
(4) 参数为空
会直接返回一个resolved状态的Promise对象
let p = Promise.resolve();
p.then(val => console.log(val)); //undefined
resolve()操作在本轮事件循环结束时执行,而非在下一轮事件循环开始时
setTimeout(() =>{
console.log(3);
} , 0)
Promise.resolve().then(() => console.log(2));
console.log(1);
//1
//2
//3
4.3 Promise.reject()
4.3.1 用法
类似于resolve(),Promise.reject()也是返回一个新的Promise()实例,不过实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null , err =>{
console.log(err) //sorry
})
传参到then/catch里的方法也是一样的
Promise.reject('sorry')
.then(null , e =>{
console.log(e); //sorry
})
//等同于
Promise.reject('sorry')
.catch(err =>{
console.log(err) //sorry
})
4.4 Promise.all()
4.4.1 用法
Promise.all()方法将多个Promise实例包装成一个新的Promise实例。
const p = Promise.all([p1 , p2 , p3]);
Promise.all()接受一个iterable类型(Array,Map,Set等)作为参数,iterable类型中每个对象都是Promise实例(若不是,就先调用Promise.resolve()转换)。
const p1 = Promise.resolve(1);
const p2 = 2;
const p3 = new Promise((resolve , reject) =>{
setTimeout(()=>{
resolve(3)
} , 1000)
})
Promise.all([p1 , p2 , p3]).then(val =>{
console.log(val) //Array(3):1 2 3
})
数组里也可以不是Promise实例,可以是常量,不过就会被忽略掉,但是仍然会被放在返回数组中。
//相当于传了个空数组 fulfilled 将数组作为参数传递
const p1 = Promise.all([1,2,3]);
//相当于只传了Promise.resolve(3) fulfilled 将数组作为参数传递
const p2 = Promise.all([1,2,Promise.resolve(3)]);
//相当于只传了Promise.reject(3) 将reject()里的3作为参数传递
const p3 = Promise.all([1,2,Promise.reject(3)]);
setTimeout(()=>{
console.log(p1); //Promise {<fulfilled>: Array(3)}
console.log(p2); //Promise {<fulfilled>: Array(3)}
console.log(p3); //Promise {<rejected>: 3} Uncaught (in promise) 3
})
4.4.2 特点
Promise.all()构造的实例的状态由参数内部Promise实例状态决定:
const p = Promise.all([p1 , p2 , p3]);
(1) p1,p2,p3的状态都为fullfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的回调函数。(就如同4.4.1中的例子)
(2) 只要p1,p2,p3中有一个rejected,p的状态就会变成rejected,此时第一个被reject的实例的返回值,会被传递给p的回调函数。
当其中一个实例被执行失败且被catch时,剩下的实例也是会执行的,因为Promise在实例化时就执行完了,then()只是用来执行接下来的回调函数。
const p1 = Promise.resolve(1);
const p2 = 2;
const p3 = new Promise((resolve , reject) =>{
setTimeout(()=>{
reject('error')
} , 1000)
})
Promise.all([p1 , p2 , p3])
.then(val =>{
console.log(val)
})
.catch(err =>{
console.log(err) //error
})
如果作为参数的Promise实例自己定义了catch方法,那么当它被rejected不会触发Promise.all()的catch方法。
注意此时all()里的then方法也有输出,因为错误已经被捕获了,p3执行完catch方法后也会变成resolved,因此p1,p2,p3还是均为resolved,调用then方法内的回调函数。
const p1 = Promise.resolve(1);
const p2 = 2;
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, 1000)
})
.catch(err => {
console.log(`p3自己就把${err}给捕获了`);
return 'err'
})
Promise.all([p1, p2, p3])
.then(val => {
console.log(val) // [1 , 2 , 'err']
})
.catch(err => {
console.log(err)
})
4.4.3 手写
具体见注释即可,理解后较为清晰:
但是要先注意一个事件循环的小问题,我们知道那道常规的for循环打印问题是由于setTimeout也属于宏任务代码要轮到下一个循环中才能执行导致的,但是似乎微任务在for循环中也要轮到最后来处理,说明for循环要执行完毕才算完成一轮宏任务?
let cnt = 0;
for(const item of [1,2,3,4,5]){
cnt++;
Promise.resolve(123)
.then(val => console.log(val))
console.log(cnt);
}
// 1 2 3 4 5
// 123*5
因此再来看all()的手写,留意到通过一个变量length来记录输入参数arr的长度,也是利用了上面这个原理:
const myPromiseAll = function (arr) {
return new Promise((resolve, reject) => {
const promiseRes = [];
// 用于遍历arr
let i = 0;
// 用于保存arr长度
let length = 0;
// 已完成的数量 用于判断啥时候返回
let fulCnt = 0;
//用for...of迭代iterator数据(因为传入的可能是array/set/map等)
for (const item of arr) {
// 计算并保存容器长度 注意这里是宏任务 能实现保存效果
i++;
length = i;
//包一层 兼容参数非Promise的情况 宏任务for执行完后才走这里的微任务
Promise.resolve(item)
.then(val => {
i--;
promiseRes[i] = val;
fulCnt++;
//完成的数量等于容器长度 返回
if (fulCnt === length) {
resolve(promiseRes);
}
})
.catch(err => {
reject(err);
})
}
// 如果传入的是空数组
if(length === 0) resolve([]);
})
}
一开始看着复杂,其实自己了解了事件循环那块后是很清晰的。
4.5 Promise.race()
4.5.1 用法和特点
大部分都和Promise.all()差不多 就不浪费过多篇幅
const p = Promise.race([p1, p2, p3]);
最大的特点是:
只要p1/p2/p3中有一个实例率先改变状态,p的状态就随之改变。率先改变的Promise实例的返回值就作为参数传递给p的回调函数。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value); //two
});
参数也是可以传基础类型的,会直接返回靠前的基础类型值。
4.5.2 手写
const myPromiseRace = function(args){
return new Promise((resolve , reject) =>{
for(const item of args){
//出现第一个promise成功或失败时就返回结果
Promise.resolve(item).then(resolve , reject);
}
})
}
其中
Promise.resolve(item).then(resolve, reject);
//相当于
Promise.resolve(item).then(val => resolve(val) , err => reject(err));
有一个地方不是特别清晰,就是for循环中怎么判断promise完成然后调用then()方法,以下是测试代码:
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1500, 'onasdasde');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
let arr = [promise1 , promise2];
for(const item of arr){
console.log('out' , item);
item.then(() => {
console.log('in' , item)
})
}
很显然处于for循环宏任务中时两个promise都还是pending状态,但是微任务中能等到规定时间后自动调用then()方法,原理感觉有点懂又不完全懂,先这么记着。
4.6 Promise.allSettled()
4.6.1 用法
Promise.allSettled()方法当参数中所有的promise都已经有了结果(fulfilled/rejected)后,返回一个对象数组。表示对应的结果。
也就是说不像all()方法,rejected后会立刻返回,allSettled()一定是所有Promise实例都有结果了后才返回。
注意allSettled()的then()的回调函数是接收一个数组,该数组的元素对应传入allSettled()数组里面的两个Promise对象。
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve , reject) =>{
setTimeout(reject , 1000 , 2);
})
Promise.allSettled([promise1 , promise2])
.then(
items => items.forEach(item => console.log(item))
//{status: 'fulfilled', value: 1}
//{status: 'rejected', reason: 2}
)
下面给出一个体现返回值的用法的例子
const promises = [fetch('index.html'), fetch('https://does-not-exist/')];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
4.6.2 手写
(1)改造Promise.all()以简便实现
allSettled()和all()最大区别就是一个遇到reject()继续执行,一个遇到reject()就返回。
一层层分析:
-
首先返回的是一个Promise.all()的结果,也就是一个Promise实例,用来做第12行的then()处理。
-
args是传进来的[p1,p2]这个实例数组,通过args.map(...)返回一个新的数组作为Promise.all()的新参数。Promise.all()会根据新参数进行返回处理。
-
then中有两种返回值,如果item的状态为resolved,比如p1,那么then方法就触发第一个回调函数,返回{status:fulfilled , value: res}这个对象作为第二步中新的数组的元素,反之亦然。
-
但是由于then返回的对象是自定义的对象而非rejected状态的promise实例,因此all()会将其作为resolved看待返回一整个数组。
const myAllSettled = function(args){
return Promise.all(args.map(item => Promise.resolve(item).then(res =>{
return {status: 'fulfilled' , value: res};
} , err =>{
return {status: 'rejected' , reason: err}
})))
}
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);
myAllSettled([p1 , p2])
.then(
results => results.forEach(res => console.log(res))
)
(2) 原始实现
和all()是类似的,搞得懂那个这个道理也差不多
const myPromiseAllSettled = function(arr){
return new Promise((resolve , reject) =>{
const res = [];
let i = 0;
let length = 0;
let fulCnt = 0;
for(const item of arr){
i++;
length = i;
Promise.resolve(item)
.then(val =>{
res[--i] = {status: 'fulfilled' , value: val};
fulCnt++;
if(fulCnt === length){
resolve(res);
}
})
.catch(err =>{
res[--i] = {status: 'rejected' , reason: err};
fulCnt++;
if(fulCnt === length){
resolve(res);
}
})
}
// 如果传入的是空数组
if(length === 0) resolve([]);
})
}
4.7 Promise.any()
4.7.1 用法
Promise.any()方法接收一组Promise实例作为参数,返回一个新的Promise实例。
只要参数实例中有一个实例变成fulfilled,返回实例就变成fulfilled
只有所有参数都变成rejected,返回实例才变成rejected。(可以发现逻辑和all()相反)
Promise.any()抛出的错误是一个AggregateError实例,这个实例对象的errors属性是一个数组,包含了所有成员的错误。
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results instanceof AggregateError); // true
console.log(results.errors); // [-1, Infinity]
});
4.7.2 手写
其实any()的手写和all()的手写就一个地方相反,别的地方均相同:
all()是成功数满了才resolve(),有一个失败就reject()
any()是有一个成功就reslove(),失败满了才reject()
因此代码也是很类似的
const myPromiseAllSettled = function(arr){
return new Promise((resolve , reject) =>{
const res = [];
let i = 0;
let length = 0;
let fulCnt = 0;
for(const item of arr){
i++;
length = i;
Promise.resolve(item)
.then(val =>{
resolve(val)
})
.catch(err =>{
res[--i] = {status: 'rejected' , reason: err};
fulCnt++;
if(fulCnt === length){
resolve(res);
}
})
}
// 如果传入的是空数组
if(length === 0) resolve([]);
})
}
5.应用
5.1. 应用举例
5.2 交替亮灯
现要求实现3s亮红灯,1s亮绿灯,2s亮黄灯交替重复实现,这是亮灯函数:
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
5.2.1 Promise写法
通过Promise的链式调用和递归实现
let task = (timer, light) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
if (light === 'green') {
green()
}
if (light === 'yellow') {
yellow()
}
resolve()
}, timer);
})
}
let execute = () => {
task(3000, 'red')
.then(() => task(1000, 'green'))
.then(() => task(2000, 'yellow'))
.then(execute)
}
execute()
这里有两个小问题需要注意:
- 两种写法的区别?
.then(() => task(1000, 'green'))
.then(task(1000, 'green'))
因为只有上面那种写法才能正确使得当promise实例fulfilled后才执行task,下面那种写法会直接执行task()
- 那么为什么要写成.then(execute)?
.then(execute)
.then(() => execute())
这两者实际上是等效的,因为execute没有接受参数,其实和上面Promise.race()的手写那里的简介写法是一个道理。
所以这里千万不能这么写,否则就犯了第一点的错误:
.then(execute())
5.2.2 async/await写法
通过async/await实现
let execute = async () => {
await task(3000, 'red')
await task(1000, 'green')
await task(2000, 'yellow')
execute()
}
execute()
6.一些看代码写输出特殊题目
Promise.resolve()
.then(() => {
return new Error("error!!!");
})
.then(res => {
console.log("then:", res);
})
.catch(err => {
console.log("catch:", err);
});
可能第一反应是走catch,实际上是输出是then: Error: error!!!
。
注意then中是return一个错误,而非throw一个错误(这种情况下才走catch)。
Promise.reject('err!!!')
.then(
(res) => { console.log('success:', res); },
(err) => { console.log('error:', err); }
)
.catch(err => { console.log('catch:', err); });
输出是error: err!!!
,then的第二个参数捕获了,catch就不会捕获了,除非是这种情况,在then的捕获中再throw一个error,才会触发catch:
Promise.reject('error!!!')
.then(
(res) => { console.log('success:', res); },
(err) => { throw new Error('new error in error callback'); }
)
.catch(err => { console.log('catch:', err); });
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
输出是1
,要注意then中只有传入函数才有效,因此第二行和第三行都是无效的,第一行的1透传到最后的console.log。
Promise.resolve("1")
.then(res => {
console.log(res);
})
.finally(() => {
console.log('finally');
});
Promise.resolve('2')
.finally(() => {
console.log('finally2');
return '我是finally2返回的值';
})
.then(res => {
console.log('finally2后面的then函数:', res);
});
输出是:
1
finally2
finally
finally2后面的then函数: 2
微任务队列变化过程如下:
将p1中的then推入
将p2中的finally2推入
执行并移出p1的then 推入p1中的finally
执行并移出p2的finally2 推入p2中的then
执行并移出p1的finally
执行并移出p2中的then
async function async1() {
console.log("async1 start");
await new Promise(resolve => {
console.log('promise1');
})
console.log('async1 success');
return 'async1 end';
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
输出是:
script start
async1 start
promise1
script end
因为await后面的Promise没有被resolve,因此下方的async1 success和async1 end都不会输出。
那么假如加上resolve呢?
async function async1() {
console.log("async1 start");
await new Promise(resolve => {
console.log('promise1');
resolve('promise1 resolve');
}).then(res => console.log(res));
console.log('async1 success');
return 'async1 end';
}
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
输出是:
script start
async1 start
promise1
script end
promise1 resolve
async1 success
async1 end
为什么async1 end能被打印出来 难道async1中不应该把return 'async1 end'改成resolve('async1 end')才能让then起作用吗?
别忘了async会将返回值包装成Promise对象,这样就相当于resolve了。
- 知识点集合
const async1 = async () => {
console.log("async1");
setTimeout(() => {
console.log('timer1');
}, 2000);
await new Promise(resolve => {
console.log('promise1');
resolve();
});
console.log('async1 end');
return "async1 success";
};
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.catch(4)
.then(res => console.log(res));
setTimeout(() => {
console.log('timer2');
}, 1000);
输出是:
script start
async1
promise1
script end
async1 end
async1 success
1
timer2
timer1
用到的知识点有这些:
- then必须传函数,否则无效。
- setTimeout不管入队的先后,时间短的先执行
7. 手写简易Promise
这里要用两个队列来存走到then的时候,状态还是pending(比如resolve被SetTimeout包裹)的情况。
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.fulfilledQueue = [];
this.rejectedQueue = [];
const resolve = value =>{
if (this.state = 'pending') {
this.state = 'fulfilled';
this.value = value;
this.fulfilledQueue.forEach(cb => cb(this.value));
}
}
const reject = reason =>{
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.rejectedQueue.forEach(cb => cb(this.reason));
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value);
} else if (this.state === 'rejected') {
onRejected(this.reason);
} else if (this.state = 'pending') {
this.fulfilledQueue.push(onFulfilled);
this.rejectedQueue.push(onRejected);
}
}
}
测试代码
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('Success!');
// reject('Error!')
}, 1000);
});
promise.then(
(value) => {
console.log('Resolved with value:', value);
},
(reason) => {
console.error('Rejected with reason:', reason);
}
);