1.异步代码的困境
在ES6出来之后,有很多关于Promise的讲解、文章,也有很多经典的书籍讲解Promise
- 虽然等你学会Promise之后,会觉得Promise不过如此;
- 但是在初次接触的时候都会觉得这个东西不好理解;
那么这里我从一个实际的例子来作为切入点:
-
我们调用一个函数,这个函数中发送网络请求(我们可以用定时器来模拟网络请求);
-
如果发送网络请求成功了,那么告知调用者发送成功,并且将相关数据返回过去;
-
如果发送网络请求失败了,那么告知调用者发送失败,并且告知错误信息;
// 第二个参数和第三个参数分别是成功的回调和失败的回调 function execCode(counter, successCallback, failureCallback) { // 异步任务 setTimeout(() => { console.log("正在网络请求"); if (counter > 0) { let total = 0; for (let i = 0; i < counter; i++) { total += i; } // 网络请求成功的回调 successCallback(total); } else { // 网络请求失败的回调 failureCallback(`${counter}这个值有问题`); } }, 3000); } execCode( 100, (value) => { console.log("网络请求成功, 请求结果:", value); }, (err) => { console.log("网络请求失败, 失败原因:", err); } ); // 网络请求成功, 请求结果: 4950 execCode( 0, (value) => { console.log("网络请求成功, 请求结果:", value); }, (err) => { console.log("网络请求失败, 失败原因:", err); } ); // 网络请求失败, 失败原因: 0这个值有问题
-
在ES6之前, 封装网络请求都是类似于上面代码的封装
-
但是我们发现有一个弊端, 在实际开发中设计网络请求函数的人和调用函数的人并不是同一个, 因此调用者必须小心翼翼, 要清楚的知道设计者是怎样设计的, 要求调用者如何传入参数, 才能放心调用, 这也是我们ES6之前的异步代码的困境
2.Promise开发中基本使用
2.1 什么是Promise
在上面的解决方案中,我们确确实实可以解决请求函数得到结果之后,获取到对应的回调,但是它存在两个主要的问题:
- 第一,我们需要自己来设计回调函数、回调函数的名称、回调函数的使用等;
- 第二,对于不同的人、不同的框架设计出来的方案是不同的,那么我们必须耐心去看别人的源码或者文档,以便可以理解它
这个函数到底怎么用;
我们来看一下Promise的API是怎么样的:
- Promise是一个类,可以翻译成 承诺、许诺 、期约;
- 当我们需要的时候,给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个Promise的对象;
- 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
- 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;
- 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
2.2 Promise代码结构
上面说起来有些抽象, 我们来看一下Promise代码结构:
const promise = new Promise((resolve, reject) => {
// 调用resolve, 执行then传入的回调
resolve("成功结果")
// 调用reject, 执行catch传入的回调
reject("错误信息")
})
promise.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
上面Promise使用过程,我们可以将它划分成三个状态:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝;
- 当执行executor中的代码时,处于该状态;
- 已兑现(fulfilled): 意味着操作成功完成;
- 执行了resolve时,处于该状态,Promise已经被兑现;
- 已拒绝(rejected): 意味着操作失败;
- 执行了reject时,处于该状态,Promise已经被拒绝;
2.3 Promise的Executor
Executor是在创建Promise时需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数:
new Promise((resolve, reject) => {
console.log("executor代码")
})
通常我们会在Executor中确定我们的Promise状态:
- 通过resolve,可以兑现(fulfilled)Promise的状态,我们也可以称之为已决议(resolved);
- 通过reject,可以拒绝(rejected)Promise的状态;
这里需要注意:一旦状态被确定下来,Promise的状态会被锁死,该Promise的状态是不可更改的
- 在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成 兑现(fulfilled);
- 在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态);
const promise = new Promise((resolve, reject) => {
resolve();
// 由于promise的状态已经确定为 已兑现, 状态确定就不会改变, 后面reject回调改变状态代码无效
reject();
});
promise.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
});
2.4 Promise重构请求
知道了Promise的结构, 我们可以将上面的案例使用Promise重构:
function execCode(counter) {
// 通过new创建Promise对象时, 我们需要传入一个回调函数
// 1.这个回调函数会被立即执行, 并且传入另外两个回调resolve和reject
const promise = new Promise((resolve, reject) => {
// 网络请求的代码
setTimeout(() => {
if (counter > 0) {
let total = 0;
for (let i = 0; i < counter; i++) {
total += i;
}
// 2.网络请求成功调用resolve回调函数时,会执行Promise对象的then方法传入的回调
resolve(total);
} else {
// 3.网络请求失败调用reject回调函数时, 会执行Promise对象的catch方法传入的回调
reject(`${counter}这个值有问题`);
}
}, 3000);
});
return promise;
}
// 1.测试请求成功
const promise1 = execCode(100);
// 请求成功执行then中的回调函数
promise1.then(value => {
console.log("网络请求成功, 请求结果:", value);
});
// 请求失败执行catch中的回调函数
promise1.catch(err => {
console.log("网络请求失败, 失败原因:", err);
});
// 2.测试请求失败
const promise2 = execCode(0);
// 请求成功执行then中的回调函数
promise2.then(value => {
console.log("网络请求成功, 请求结果:", value);
});
// 请求失败执行catch中的回调函数
promise2.catch(err => {
console.log("网络请求失败, 失败原因:", err);
});
// 3.我们可以将上面代码合并起来简写
const promise3 = execCode(10)
promise3.then(value => {
console.log("网络请求成功, 请求结果:", value);
}).catch(err => {
console.log("网络请求失败, 失败原因:", err);
})
掌握以上promise的使用方法, 开发中promise已经基本没有问题, 接下来的知识理解即可, 在开发中很少用到
2.5 resolve不同值的区别
情况一:如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
const promise = new Promise((resolve, reject) => {
resolve([
{ name: "iphone", price: 5000 },
{ name: "xiaomi", price: 3000 },
]);
});
promise.then((res) => {
console.log(res); // [{ name: "iphone", price: 5000 },{ name: "xiaomi", price: 3000 }]
});
情况二:如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态, 并且新的Promise会等待原Promise的决议的结果作为自己决议的结果:(了解, 开发中基本不会这样写)
const p = new Promise((resolve) => {
setTimeout(() => {
console.log("p的resolve");
}, 2000);
});
const promise = new Promise((resolve, reject) => {
resolve(p);
});
promise.then((res) => {
console.log(res); // p的resolve
});
情况三:如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结
果来决定Promise的状态:(了解, 开发中基本不会这样写)
const promise = new Promise((resolve, reject) => {
resolve({
name: "kaisa",
then: function (resolve) {
resolve(1111);
},
});
});
promise.then((res) => {
console.log(res); // 1111
});
3.Promise的细节补充
3.1 then方法使用的补充
3.1.1 then方法接收两个参数
then方法是Promise对象上的一个方法(实例方法):
- 它其实是放在Promise的原型上的 Promise.prototype.then
then方法可以接受两个参数:
- fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
- rejected的回调函数:当状态变成reject时会回调的函数;
const promise = new Promise((resolve, reject) => {
// resolve("成功信息");
reject("失败信息");
});
promise.then(res => {
console.log("成功的回调", res);
},err => {
console.log("失败的回调", err);
}
);
上面这种写法也是可以的, 但是我们更推荐下面这种写法
const promise = new Promise((resolve, reject) => {
// resolve("成功信息");
reject("失败信息");
});
promise.then(res => {
console.log("成功的回调", res);
},err => {
console.log("失败的回调", err);
}
);
3.1.2 then方法多次调用
一个Promise的then方法是可以被多次调用的:
- 每次调用我们都可以传入对应的fulfilled回调;
- 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
const promise = new Promise((resolve, reject) => {
resolve("成功信息");
});
promise.then((res) => {
console.log("成功的回调", res); // 成功的回调 成功信息
});
promise.then((res) => {
console.log("成功的回调", res); // 成功的回调 成功信息
});
promise.then((res) => {
console.log("成功的回调", res); // 成功的回调 成功信息
});
promise.then((res) => {
console.log("成功的回调", res); // 成功的回调 成功信息
});
3.1.3 then方法的返回值
then方法本身是有返回值的,它的返回值是一个Promise,所以我们可以进行如下的链式调用:
const promise = new Promise((resolve, reject) => {
resolve("aaa");
});
// 链式调用
promise.then().then().then().then();
但是then方法返回的Promise到底处于什么样的状态呢?
Promise有三种状态,那么这个then方法返回的Promise处于什么状态呢?
当then方法中的回调函数本身在执行的时候,那么它处于等待(pending)状态;
当then方法中的回调函数返回一个结果时,那么它处于决议状态,并且会将回调函数的结果作为resolve的参数;
-
情况一:回调函数返回的是一个普通的值;
then方法是返回一个新的Promise, 这个新的Promise的兑现(决议)是等到then方法传入的回调函数有返回值时, 将then方法中的回调函数的返回值, 作为新的Promise的兑现(决议)的结果
const promise = new Promise((resolve, reject) => { resolve("aaa"); }); promise .then((res) => { console.log("第一个then方法:", res); // 第一个then方法: aaa return "bbb"; }) .then((res) => { console.log("第二个then方法:", res); // 第二个then方法: bbb })
上面代码中, 它并不是直接将函数返回值作为下一个then方法的res, 我们可以将then方法中的回调函数看做fn, 我们简单理解一下它内部的原理
// 上面代码中return相当于执行下面的操作 // 我们将第一个then方法的回调函数看做一个fn // then方法的返回值是一个Promise, 第一个then方法相当于内部创建了一个新的Promise new Promise((resolve, reject) => { // 这个创建出来的新Promise内部会将上一个then方法中的回调函数fn的返回值保存 const result = fn() // 并将这个返回值作为这个新的Promise的决议(兑现)结果 resolve(result) }) // 因此我们第二个then方法回调函数中的res拿到的就是新的Promise的result
-
情况二:返回一个Promise;
如果第一个then方法的回调函数, 返回值是一个Promise会怎样执行呢 ?
- 第一个then方法返回值同样是一个新的Promise, 这个新的Promise的resolve中传入的又是newPromise这个Promise
- 新的Promise会等待newPromise的决议结果, 并将newPromise的决议结果作为新的Promise的决议结果
- 因此**第二个then方法回调函数中的res其实是newPromise的决议结果
-
情况三:返回一个thenable值;
const promise = new Promise((resolve, reject) => { resolve("aaa"); }); const newPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve("kaisa"); }, 3000); }); promise .then((res) => { console.log("第一个then方法:", res); // 第一个then方法: aaa // 让回调函数返回一个Promise return newPromise; }) .then((res) => { console.log("第二个then方法:", res); // 第二个then方法: kaisa });
then方法中回调函数返回的是一个含有then方法的对象
const promise = new Promise((resolve, reject) => { resolve("aaa"); }); promise .then((res) => { console.log("第一个then方法:", res); // 第一个then方法: aaa // 返回一个then方法的对象 return { then: function (resolve) { resolve("thenablel"); }, }; }) .then((res) => { console.log("第二个then方法:", res); // 第二个then方法: thenablel });
当then方法抛出一个异常时,那么它处于reject状态, 下面会演示;
3.2 catch方法使用的补充
3.2.1 catch方法多次调用
catch方法也是Promise对象上的一个方法(实例方法):
- 它也是放在Promise的原型上的 Promise.prototype.catch
一个Promise的catch方法是可以被多次调用的:
- 每次调用我们都可以传入对应的reject回调;
- 当Promise的状态变成reject的时候,这些回调函数都会被执行;
const promise = new Promise((resolve, reject) => {
reject("访问失败");
});
promise.catch((err) => {
console.log("err:", err); // err:访问失败
});
promise.catch((err) => {
console.log("err:", err); // err:访问失败
});
promise.catch((err) => {
console.log("err:", err); // err:访问失败
});
promise.catch((err) => {
console.log("err:", err); // err:访问失败
});
3.2.2 catch方法的返回值
事实上catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法:
- reject方法会调用最近的一个catch方法, 即使不是同一个Promise, 例如下面代码, 第一个promise中的reject找到的并不是本身的catch方法, 而是第三个then方法返回的Promise中的catch方法
const promise = new Promise((resolve, reject) => {
reject("错误信息")
})
promise.then(res => {
console.log("第一个then的回调", res)
}).then(res => {
console.log("第二个then的回调", res)
}).then(res => {
console.log("第三个then的回调", res)
}).catch(err => {
console.log("catch回调被执行", err) // catch回调被执行 错误信息
})
- 上面代码中, 如果我们想第二个或者第三个then方法返回的Promise中, 执行reject, 因为then方法中没有reject方法, 那需要怎么做呢?
- 这个时候我就需要抛出一个异常
const promise = new Promise((resolve, reject) => {
resolve("aaa")
})
promise.then(res => {
console.log("第一个then的回调", res)
// 抛出异常 可以执行then方法返回的Promise的reject
// throw new Error("第二个Promise的异常")
return "bbb"
}).then(res => {
console.log("第二个then的回调", res)
// 抛出异常 可以执行then方法返回的Promise的reject
throw new Error("第三个Promise的异常")
return "ccc"
}).catch(err => {
console.log("catch回调被执行", err) // catch回调被执行 Error: 第二个Promise的异常 at index.html:186:17
})
小结: 总的来说, catch并不是某一个Promise被拒绝时才会回调, 例如上面代码中, 不管是哪一个Promise被拒绝, 都会回调catch
4.finally方法
finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行
的代码。
finally方法是不接收参数的,因为无论前面是已决议 (fulfilled) 状态,还是已拒绝 (rejected) 状态,它都会执行。
const promise = new Promise((resolve, reject) => {
resolve();
// reject()
});
promise.then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
}).finally(() => {
// 不管是什么状态都会执行finally方法中的代码
console.log("哈哈哈哈");
console.log("呵呵呵呵");
});
5.Promise的类方法
前面我们学习的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的。
- 下面我们再来学习一下Promise的类方法。
5.1 resolve方法
有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
- Promise.resolve的用法相当于new Promise,并且执行resolve操作:
Promise.resolve("coder")
// 等价于
const promise = new Promise((resolve) => resolve("coder"))
resolve参数的形态:
- 情况一:参数是一个普通的值或者对象
- 情况二:参数本身是Promise
- 情况三:参数是一个thenable
5.2 reject方法
reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
- Promise.reject的用法相当于new Promise,只是会调用reject:
Promise.reject("error")
// 等价于
const promise = new Promise((resolve, reject) => {
reject("error")
})
注意: Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的。
5.3 all方法
另外一个类方法是Promise.all:
-
它的作用是将多个Promise包裹在一起形成一个新的Promise;
-
新的Promise状态由包裹的所有Promise共同决定:
-
当所有的Promise状态变成已兑现 (fulfilled) 状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
// 创建三个Promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p1 resolve") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p2 resolve") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 5000) }) // 会等待五秒, 当所有Promise都有结果才会打印 Promise.all([p1, p2, p3]).then(res => { console.log("all Promise res:", res) // all Promise res: ['p1 resolve', 'p2 resolve', 'p3 resolve'] })
-
当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
// 创建三个Promise const p1 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p1 resolve") reject("p1 reject") }, 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { // resolve("p2 resolve") reject("p2 reject") }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve("p3 resolve") }, 5000) }) // p1 p2都有返回reject, 会将第一个reject的返回值, 也就是p2的返回值作为参数 Promise.all([p1, p2, p3]).then(res => { console.log("all Promise res:", res) }).catch(err => { console.log("all Promise err:", err) // all Promise err: p2 reject })
-
应用场景: 在开发中, 假如我们有三个网络请求, 我们想要三个网络请求都有结果后, 一起返回给我们就可以使用Promise.all方法
5.4 allSettled方法
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
- 那么对于已决议(resolved)状态的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
在ES11(ES2020)中,添加了新的API Promise.allSettled:
- 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态;
- 并且这个Promise的结果一定是fulfilled的;
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p1 resolve")
reject("p1 reject")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p2 resolve")
reject("p2 reject")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
}, 5000)
})
// allSettled方法
Promise.allSettled([p1, p2, p3]).then(res => {
console.log("all allSettled:", res)
})
我们来看一下打印结果:
all allSettled: [
{status: 'rejected', reason: 'p1 reject'},
{status: 'rejected', reason: 'p2 reject'},
{status: 'fulfilled', value: 'p3 resolve'}
]
- allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
- 这个对象中包含status状态,以及对应的value值;
5.5 race方法
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:
- race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p1 resolve")
reject("p1 reject")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p2 resolve")
reject("p2 reject")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
}, 5000)
})
// 由于p2最先返回一个结果 并且是已拒绝状态 那么会执行catch的回调
Promise.race([p1, p2, p3]).then(res => {
console.log("race res:", res)
}).catch(err => {
console.log("race err:", err) // race err: p2 reject
})
5.6 any方法
any方法是ES12中新增的方法,和race方法是类似的:
- any方法会等到一个fulfilled状态,才会决定新Promise的状态;
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1 resolve")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve("p2 resolve")
reject("p2 reject")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p3 resolve")
}, 5000)
})
// 最先返回的p2 状态是已拒绝, 那么便会等下一个是p1 状态是以决议 就会返回p2
Promise.any([p1, p2, p3]).then(res => {
console.log("any promise res:", res) // any promise res: p1 resolve
})
- 如果所有的Promise都是rejected的,那么也会等到所有的Promise都变成rejected状态, 再执行catch中的回调;
// 创建三个Promise
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p1 reject")
}, 3000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p2 reject")
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p3 reject")
}, 5000)
})
// 当所有Promise的都是rejected时
Promise.any([p1, p2, p3]).then(res => {
console.log("any promise res:", res)
}).catch(err => {
console.log("any promise err:", err) // any promise err: AggregateError: All promises were rejected
})
如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/120131.html