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

Promise简介

Promise 最先出现是为了解决编程中的异步行为致使的回调地狱。在没有 Promise 以前,对于函数的异步行为,通常采用回调函数的方式,在一个函数调用结束触发回调函数,这样会致使多层级的回调函数,难以维护。 Promise 有两个参数,一个成功回调,一个失败回调,因此在 Promise 外部,能够准确的得到成功和失败的时机,而且 Promise 支持链式调用,这样能够方便的进行屡次调用,可是永远都是单层级,便于维护。javascript

两种异步行为对比

回调地狱方式html

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 写法java

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 引擎预约义的,不要咱们建立,咱们只须要在咱们想要告知的状态中调用对应的方法便可。promise

let promise = new Promise(function(resolve, reject) {
  resolve("done");

  reject(new Error("…")); // 被忽略
  setTimeout(() => resolve("…")); // 被忽略
});
复制代码

这儿只能有一个结果或一个 error executor 只能调用一个 resolve 或一个 reject。任何状态的更改都是最终的。 全部其余的再对 resolvereject 的调用都会被忽略 而且,resolve/reject 只须要一个参数(或不包含任何参数),而且将忽略额外的参数markdown

那么咱们会疑问,咱们费这么大工夫,在 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 提供一个函数参数:oop

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

复制代码
相关文章
相关标签/搜索