阅读 295

Promise基础到手写(使用+构造)

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。任何状态的更改都是最终的。 所有其他的再对 resolvereject 的调用都会被忽略 并且,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。 首先我们要分析一下我们的需求,我们要得到什么,要实现哪些功能,确定目标。

  1. 我们要实现一个名叫 Promise 的类。
  2. 类里我们要实现一个 resolve 成功通知。
  3. 类里我们要实现一个 reject失败通知。
  4. executor 立刻执行。
  5. 我们还要实现一个可以拿到结果的 then。
  6. 一个捕获错误的 catch。
  7. 一个不管结果的 finally。

Promise.png 我们按照上图,分为两个大步骤,开始进行实现我们自己的 Promise。

首先构造 Promise 类。

  1. 初始化阶段,我们考虑到 Promise 一共有三种状态,两个结果。所以我们要初始化状态和结果。
  2. 然后我们发送成功和失败的信息时,要改变状态,并且保存结果。
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() {}
}

复制代码

我们实现了上述几个目标,接下来我们要实现接受结果信息的方法。

  1. then 接受两个参数,第一个将在 promise resolved 后运行并接收 value 。第二个将在 promise reject 后运行并接收 error。
  2. catch 只接受一个函数,promise reject 后运行并接收 error。
  3. 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 ) 做的事情

  1. 入参判断,处理 onFulfilled 或者 onRejected 不是函数的情况。
  2. 创建并且返回一个 promise 实例。
  3. 将 onFulfilled 和 onRejected 添加到事件队列(根据 promise 的状态来决定如何处理)。
  4. 状态为 fulfilled 执行 onFulfilled 。
  5. 状态为 rejected 则执行 onRejected。
  6. 如果没有做出决议,则添加进事件队列。
  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 的结果。 我们来看一张流程图,然后我们按照流程图来实现我们的代码

PromiseAll.png

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);
          }
        });
    }
  });
};

复制代码
文章分类
前端
文章标签