Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入了解Promise #15

Open
Yuanfang-fe opened this issue Apr 16, 2021 · 0 comments
Open

深入了解Promise #15

Yuanfang-fe opened this issue Apr 16, 2021 · 0 comments

Comments

@Yuanfang-fe
Copy link
Owner

Yuanfang-fe commented Apr 16, 2021

目的

  1. 了解promise的各种用法
  2. 了解promise原理,并手动实现

API

静态方法

  • Promise.all(iterable)

多个 Promise 任务同时执行,如果全部成功执行,则以数组的方式返回所有 Promise 任务的执行结果。 如果有一个 Promise 任务 rejected,则只返回 rejected 任务的结果。

  • Promise.race(iterable)

多个 Promise 任务同时执行,返回最先执行结束的 Promise 任务的结果,不管这个 Promise 结果是成功还是失败。

  • Promise.allSettled(iterable)

多个 Promise 任务同时执行,等到所有promises都已敲定(settled)(每个promise都已兑现(fulfilled)或已拒绝(rejected))。返回一个promise,该promise在所有promise完成后完成。并带有一个对象数组,每个对象对应每个promise的结果。

  • Promise.any(iterable)

多个 Promise 任务同时执行,当其中的一个 promise 成功,就返回那个成功的promise的值。

  • Promise.reject(reason)

多个 Promise 任务同时执行,并将给定的失败信息传递给对应的处理方法。

  • Promise.resolve(value)

返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;
否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。
通常而言,如果您不知道一个值是否是Promise对象,使用Promise.resolve(value) 来返回一个Promise对象,这样就能将该value以Promise对象形式使用。

原型方法

  • Promise.prototype.catch(onRejected)

实例方法,捕获异常,函数形式:fn(err){}, err 是 catch 注册 之前的回调抛出的异常信息。

  • Promise.prototype.then(onFulfilled, onRejected)

Promise 注册回调函数,函数形式:fn(vlaue){},value 是上一个任务的返回结果,then 中的函数一定要 return 一个结果或者一个新的 Promise 对象,才可以让之后的then 回调接收。

  • Promise.prototype.finally(onFinally)

添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)

Promise解决了什么问题?

  • 回调地域

如果有个网络请求,我们一般会这么写

getUserInfo(function(res) {
    // do some thing
})

如果再这个请求后的基础上,还要再发请求, 当请求变多了之后,就变成下面的样子:

getUserInfo(function() {
    getOrderList(function() {
       getOrderInfo(function() {
           getA(function() {
               getB(function() {
                    // do some thing
                })
           })
       })
    })
})

这就是江湖上令人闻风丧胆的”回调地狱“,它的缺点有:

  • 代码臃肿。
  • 可读性差。
  • 耦合度过高,可维护性差。
  • 代码复用性差。
  • 容易滋生 bug。
  • 只能在回调里处理异常。

那么我们能不能以更易阅读的同步方法来实现呢?promise 为我们提供了解决方案

const getUserInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(1);
    }, 1000);
  });
};

const getOrderList = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(2);
    }, 100);
  });
};

const getOrderInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(3);
    }, 200);
  });
};

const getA = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(4);
    }, 300);
  });
};

const getB = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(5);
    }, 400);
  });
};

getUserInfo()
  .then((res) => {
    console.log(res);
    return getOrderList();
  })
  .then((res) => {
    console.log(res);
    return getOrderInfo();
  })
  .then((res) => {
    console.log(res);
    return getA();
  })
  .then((res) => {
    console.log(res);
    return getB();
  })
  .catch((err) => {
    console.log(err);
  })
  .finally(() => {
    console.log("end");
  });

控制台打印结果:
1 2 3 4 end

这样确实要比回调地域好一些了,但是跟同步写法还有不太一样,那我们用async、await 同步写法优化一下

(async function useAsync() {
  try {
    const A = await getUserInfo();
    console.log(A);
    const B = await getOrderList();
    console.log(B);
    const C = await getOrderInfo();
    console.log(C);
    const D = await getA();
    console.log(D);
    const F = await getB();
    console.log(F);
  } catch (error) {
    console.log("error:" + error);
  }
})();

明白了promise的用法,那我们手动实现一个Promise试试

手写Promise

先写个简单的Promise:

function Promise(fn) {
  let callbacks = [];

  this.then = function (onResolved) {
    callbacks.push(onResolved);
  };

  function resolve(value) {
    callbacks.forEach((cb) => {
      cb(value);
    });
  }

  fn(resolve);
}

const getUserInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(1);
    }, 1000);
  });
};

getUserInfo().then((res) => {
  console.log(res);  // 控制台输出:1
});

ok,现在还非常简陋,现在还不支持连续的调用then, 通过上面的文档我们可以知道,then是有返回一个promise的:

  this.then = function (onResolved) {
    callbacks.push(onResolved);
    return this;
  };

// 调用
getUserInfo()
  .then((res) => {
    console.log(res);
  })
  .then((res) => {
    console.log(2);
  });
输出:1 2

对于promise而言,resolve才是执行函数的方法,那这样就需要保证在resolve之前,then方法中所有函数都已经push到数组里
这个条件,我们可以用setTimeout(fn, 0)实现,setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。
不懂可以看这里

function resolve(value) {
    setTimeout(function () {
      callbacks.forEach((cb) => {
        cb(value);
      });
    }, 0);
  }

我们现在加几个请求,完善后,函数长这个样子:

function Promise(fn) {
  let callbacks = [];

  this.then = function (onResolved) {
    callbacks.push(onResolved);
    return this;
  };

  function resolve(value) {
    setTimeout(function () {
      callbacks.forEach((cb) => {
        cb(value);
      });
    }, 0);
  }

  fn(resolve);
}

const getUserInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(1);
    }, 100);
  });
};

const getOrderList = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(2);
    }, 3000);
  });
};

const getOrderInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(3);
    }, 5000);
  });
};

getUserInfo()
  .then((res) => {
    console.log("res1:" + res);
    return getOrderList();
  })
  .then((res) => {
    console.log("res2:" + res);
    return getOrderInfo();
  })
  .then((res) => {
    console.log("res3:" + res);
  });

控制台会输出:res1:1 res2:1 res3:1, 现在有两个问题,1是返回值不对,2是没有按定时的时间点返回,二是几乎按顺序同时返回的
在resolve函数中加个返回值看下:

  function resolve(value) {
    console.log(value)
    setTimeout(function () {
      callbacks.forEach((cb) => {
        cb(value);
      });
    }, 0);
  }

输出:1 res1:1 res2:1 res3:1 2 3

因为resolve中的callbacks没有等前面定制器结束再执行,而是直接全部执行了,所以我们需要加入状态。

promise 中的状态有 pending、fulfilled、rejected。

pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。

promiseState

加入状态后的代码长长这样:

function Promise(fn) {
  let state = "pending";
  let callbacks = [];
  let value = null;

  function handle(handler) {
    if (state === "pending") {
      callbacks.push(handler);
      return;
    }

    let cb = state === "fulfilled" ? handler.onResolved : handler.onRejected,
      ret;
    if (cb === null) {
      cb = state === "fulfilled" ? handler.resolve : handler.reject;
      cb(value);
      return;
    }
    try {
      ret = cb(value);
    } catch (e) {
      handler.reject(e);
      return;
    }
    handler.resolve(ret);
  }

  this.then = function (onResolved, onRejected) {
    return new Promise(function (resolve, reject) {
      handle({
        onResolved: onResolved || null,
        onRejected: onRejected || null,
        resolve: resolve,
        reject: reject
      });
    });
  };

  function resolve(newValue) {
    if (
      newValue &&
      (typeof newValue === "object" || typeof newValue === "function")
    ) {
      var then = newValue.then;
      if (typeof then === "function") {
        then.call(newValue, resolve, reject);
        return;
      }
    }

    value = newValue;
    state = "fulfilled";

    execute();
  }

  function reject(reason) {
    state = "rejected";
    value = reason;
    execute();
  }

  function execute() {
    setTimeout(function () {
      callbacks.forEach(function (cb) {
        handle(cb);
      });
    }, 0);
  }

  fn(resolve, reject);
}

const getUserInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(1);
    }, 100);
  });
};

const getOrderList = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      reject(2);
    }, 1000);
  });
};

const getOrderInfo = function () {
  return new Promise((resolve, reject) => {
    setTimeout(function () {
      resolve(3);
    }, 1000);
  });
};

getUserInfo()
  .then((res) => {
    console.log("res1:" + res);
    return getOrderList();
  })
  .then(
    (res) => {
      console.log("res2:" + res);
      return getOrderInfo();
    },
    (res) => {
      console.log("res2 error:" + res);
    }
  )
  .then((res) => {
    console.log("res3:" + res);
  });

感觉总结的不好,后来偶尔看到这篇 https://juejin.cn/post/6844903665686282253 ,作者总结的很清晰,有空再来改写一遍。

参考:
https://mengera88.github.io/2017/05/18/Promise%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://juejin.cn/post/6844903625609707534#heading-0
https://segmentfault.com/a/1190000013538587

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant