Promise简介
Promise 最早出现是为了解决编程中的异步行为导致的回调地狱。在没有 Promise 之前,对于函数的异步行为,一般采用回调函数的方式,在一个函数调用结束触发回调函数,这样会导致多层级的回调函数,难以维护。 Promise 有两个参数,一个成功回调,一个失败回调,所以在 Promise 外部,可以准确的获得成功和失败的时机,并且 Promise 支持链式调用,这样可以方便的进行多次调用,但是永远都是单层级,便于维护。
两种异步行为对比
回调地狱方式
var sayhello = function (name, callback) {
setTimeout(function () {
console.log(name);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
//输出: first second third end
Promise 写法
let sayHello = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("first");
resolve();
}, 1000);
});
sayHello
.then(() => {
console.log("second");
})
.then(() => {
console.log("third");
})
.then(() => {
console.log("end");
});
我们可以发现 Promise 不管有多少次逻辑处理,每一次只有一层,清晰可见,不像回调一样,层层嵌套,难以理清。
Promise 基础
Promise 实例
let promise = new Promise(
function(resolve, reject) { // executor(执行者)
setTimeout(()=>{
resolve("done"),1000);
});
我们只需要 new Promise即可创建一个 Promise,创建即立刻调用其中的执行者。 executor 接受两个参数:resolve 和 reject 。这些是JavaScript 引擎预定义的,不要我们创建,我们只需要在我们想要告知的状态中调用对应的方法即可。
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // 被忽略
setTimeout(() => resolve("…")); // 被忽略
});
这儿只能有一个结果或一个 error
executor 只能调用一个 resolve
或一个 reject
。任何状态的更改都是最终的。
所有其他的再对 resolve
和 reject
的调用都会被忽略
并且,resolve/reject
只需要一个参数(或不包含任何参数),并且将忽略额外的参数
那么我们会疑问,我们费这么大工夫,在 Promise 内部做这么多操作,最后使他产生一个状态是为了什么,他失败与否和我们之前的回调地狱有什么关系?
state 和 result 都是内部的
Promise 对象的 state 和 result 属性都是内部的。
我们无法直接访问它们。但我们可以对它们使用 .then/.catch/.finally 方法。
我们在下面对这些方法进行了描述。
上面我们使用 Promise 生产了一个成功或者失败的结果,可以通过使用 .then
、.catch
和 .finally
方法为消费函数进行结果接收。
then
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 运行 .then 中的第一个函数
promise.then(
result => alert(result), // 1 秒后显示 "done!"
error => alert(error) // 不运行
);
.then
的第一个参数是一个函数,该函数将在 promise resolved 后运行并接收结果。
.then
的第二个参数也是一个函数,该函数将在 promise rejected 后运行并接收 error。
如果我们只对成功完成的情况感兴趣,那么我们可以只为 .then
提供一个函数参数:
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1 秒后显示 "done!"
.catch(f)
调用是 .then(null, f)
的完全的模拟,它只是一个简写形式。简单说,catch就是一个接收错误结果的方法。
我们可以对 settled 的 promise 附加处理程序
如果 promise 为 pending 状态,.then/catch/finally
处理程序(handler)将等待它。否则,如果 promise 已经是 settled 状态,它们就会运行
自测案例
写一个 3s 后弹窗的 Promise
function delay(ms) {
// 你的代码
return new Promise(resolve,reject){
setTimeout(resolve,3000)
}
}
delay(3000).then(() => alert('runs after 3 seconds'));
catch
如果只要失败回调,那么只需要将 then 的第一个参数设置为null, 也可以使用 catch ,这里面可以接受 reject 的结果
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f) 与 promise.then(null, f) 一样
promise.catch(alert); // 1 秒后显示 "Error: Whoops!"
finally
无论结果如何,最后都会执行这里面的函数。
**finally()**
方法返回一个Promise
。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise
是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在then()
和catch()
中各写一次的情况。
构建 Promise
在我了解完 Promise 后,我对如何实现他非常感兴趣,于是我试着自己来构建一个 Promise。 首先我们要分析一下我们的需求,我们要得到什么,要实现哪些功能,确定目标。
- 我们要实现一个名叫 Promise 的类。
- 类里我们要实现一个 resolve 成功通知。
- 类里我们要实现一个 reject失败通知。
- executor 立刻执行。
- 我们还要实现一个可以拿到结果的 then。
- 一个捕获错误的 catch。
- 一个不管结果的 finally。
我们按照上图,分为两个大步骤,开始进行实现我们自己的 Promise。
首先构造 Promise 类。
- 初始化阶段,我们考虑到 Promise 一共有三种状态,两个结果。所以我们要初始化状态和结果。
- 然后我们发送成功和失败的信息时,要改变状态,并且保存结果。
class Promise {
constructor(executor) {
if (typeof executor !== "function") {
console.log("参数不合法");
} else {
this.status = "pending";
this.value = undefined;
this.error = undefined;
//自动运行Promise中的函数
try {
//将resolve和reject函数给使用者
executor(resolve, reject);
} catch (e) {
//如果在函数中抛出异常则将它注入reject中
reject(e);
}
}
}
resolve(data) {
//成功触发
if (this.status === "pending") {
this.status = "fulfilled";
this.value = data;
}
}
reject(data) {
//失败触发
if (this.status === "pending") {
this.status = "rejected";
this.error = data;
}
}
then() {}
catch() {}
finally() {}
}
我们实现了上述几个目标,接下来我们要实现接受结果信息的方法。
- then 接受两个参数,第一个将在 promise resolved 后运行并接收 value 。第二个将在 promise reject 后运行并接收 error。
- catch 只接受一个函数,promise reject 后运行并接收 error。
- finally 无论结果如何都会执行。
class Promise {
constructor(executor) {
if (typeof executor !== "function") {
console.log("参数不合法");
} else {
this.status = "pending";
this.value = undefined;
this.error = undefined;
//自动运行Promise中的函数
try {
//将resolve和reject函数给使用者
executor(this.resolve, this.reject);
} catch (e) {
//如果在函数中抛出异常则将它注入reject中
this.reject(e);
}
}
}
resolve = (data) => {
//成功触发
if (this.status === "pending") {
this.status = "fulfilled";
this.value = data;
}
};
reject = (data) => {
//失败触发
if (this.status === "pending") {
this.status = "rejected";
this.error = data;
}
};
then(onFulfilled, onRejected) {
if (this.status === "fulfilled") {
onFulfilled(this.value);
}
if (this.status === "rejected") {
onRejected(this.error);
}
}
catch(onRejected) {
if (this.status === "rejected") {
onRejected(this.error);
}
}
finally(onFinally) {
if (this.status !== "pending") {
onFinally();
}
}
}
这样,我们就完成了一个简易版的 Promise。 我们来将文件引入测试一下,看看结果如何。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./index.js"></script>
<script>
let promise = new Promise((resolve, reject) => {
resolve("coolFish!");
});
promise.then(
(data) => {
console.log("成功" + data);
},
(data) => {
console.log("失败" + data);
}
);
</script>
</body>
</html>
结果和 Promise 一样,可以实现成功和失败的不同操作,接下来我们要开始扩展它的功能,从可支持链式调用开始。
Promise.then
首先我们明确 promise.then(onFulfilled, onRejected ) 做的事情
- 入参判断,处理 onFulfilled 或者 onRejected 不是函数的情况。
- 创建并且返回一个 promise 实例。
- 将 onFulfilled 和 onRejected 添加到事件队列(根据 promise 的状态来决定如何处理)。
- 状态为 fulfilled 执行 onFulfilled 。
- 状态为 rejected 则执行 onRejected。
- 如果没有做出决议,则添加进事件队列。
then(onFulfilled, onRejected) {
//创建并返回一个Promise实例
return new Promise((resolve, reject) => {
let wrapOnFulfilled = () => {
setTimeout(() => {
try {
console.log("wrapOnFulfilled");
let x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
}, 0);
};
let wrapOnRejected = () => {
setTimeout(() => {
try {
console.log("wrapOnRejected");
let x = onRejected(this.error);
resolve(x);
} catch (error) {
reject(error);
}
}, 0);
};
if (this.status === "fulfilled") {
wrapOnFulfilled();
} else if (this.status === "rejected") {
wrapOnRejected();
} else {
this.onFulfilledCallbacks.push(wrapOnFulfilled);
this.onRejectedCallbacks.push(wrapOnRejected);
}
});
}
Promise.all
首先我们先明确目标
Promise.all
接受一个 promise 数组作为参数(从技术上讲,它可以是任何可迭代的,但通常是一个数组)并返回一个新的 promise。
当所有给定的 promise 都被 settled 时,新的 promise 才会 resolve,并且其结果数组将成为新的 promise 的结果。
我们来看一张流程图,然后我们按照流程图来实现我们的代码
all(promises) {
return new Promise((resolve, reject) => {
// 如果Promise.all接收到的是一个空数组([]),它会立即决议。
if (!promises.length) {
resolve([]);
}
let result = [];
let resolvedPro = 0;
for (let index = 0, length = promises.length; index < length; index++) {
Promise.resolve(promises[index]).then(
(data) => {
// 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。
result[index] = data;
if (++resolvedPro === length) {
resolve(result);
}
},
(error) => {
reject(error);
}
);
}
});
}
Promise.race
// 需要注意的是,如果Promise.race接收到的是一个空数组([]),则会一直挂起,而不是立即决议。
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach((promise) => {
Promise.resolve(promise).then(resolve, reject);
});
});
};
Promise.allSettled
// Promise.allSettled 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,
// 并带有一个对象数组,每个对象表示对应的promise结果。
Promise.allSettled = function(promises) {
return new Promise((resolve, reject) => {
if (!promises.length) {
resolve([]);
}
let result = [];
let resolvedPro = 0;
for (let index = 0, length = promises.length; index < length; index++) {
Promise.resolve(promises[index])
.then((data) => {
// 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。
result[index] = {
status: FULFILLED_STATE,
value: data,
};
if (++resolvedPro === length) {
resolve(result);
}
})
.catch((error) => {
result[index] = {
status: REJECTED_STATE,
reason: error,
};
if (++resolvedPro === length) {
resolve(result);
}
});
}
});
};
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!