技术咨询、项目合作、广告投放、简历咨询、技术文档下载 点击这里 联系博主

手写 promise

# Promise 介绍

引用阮一峰老师的话:

(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

Promise 的作用:

  • 解决嵌套地狱问题;
  • 解决信任问题: 除去书写的不优雅和维护的困难以外,回调函数其实还存在信任问题。
$.ajax("xxxxxx", function success(result1) {
  //回调函数不一定会像你期望的那样被调用。因为控制权不在你的手上,不能抱歉此函数一定会被调用
});

# 要求实现 polyfill

function Promise(executor) {
  var self = this;
  self.value = undefined;
  self.status = "pending";
  self.onResolvedCallback = [];
  self.onRejectedCallback = [];

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject);
    }
    //原则上,promise.then(onResolved, onRejected)里的这两相函数需要异步调用,关于这一点,标准里也有说明:https://promisesaplus.com/#point-67
    setTimeout(function() {
      // 异步执行所有的回调函数
      if (self.status === "pending") {
        self.status = "resolved";
        self.data = value;
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value);
        }
      }
    });
  }

  function reject(reason) {
    setTimeout(function() {
      // 异步执行所有的回调函数
      if (self.status === "pending") {
        self.status = "rejected";
        self.data = reason;
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason);
        }
      }
    });
  }

  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason);
  }
}
/**
@params promise2 当前then返回的promise
@params x 执行了当前then的函数,返回的结果
@resolve 当前then返回的promise 的resolve
@reject 当前then返回的promise 的reject
**/
function resolvePromise(promise2, x, resolve, reject) {
  var then;
  var thenCalledOrThrow = false;

  if (promise2 === x) {
    return reject(new TypeError("Chaining cycle detected for promise!"));
  }
  // 执行了当前then的函数,返回的结果还是promise则需要先处理then返回的promise

  if (x instanceof Promise) {
    // 如果x的状态还没有确定,那么它是有可能被一个thenable决定最终状态和值的
    // 所以这里需要做一下处理,而不能一概的以为它会被一个“正常”的值resolve
    if (x.status === "pending") {
      x.then(function(v) {
        resolvePromise(promise2, v, resolve, reject);
      }, reject);
    } else {
      // 但如果这个Promise的状态已经确定了,那么它肯定有一个“正常”的值,而不是一个thenable,所以这里直接取它的状态
      x.then(resolve, reject);
    }
    return;
  }
  // 如果then执行的结果为Object/function
  if (x !== null && (typeof x === "object" || typeof x === "function")) {
    try {
      // 2.3.3.1 因为x.then有可能是一个getter,这种情况下多次读取就有可能产生副作用
      // 即要判断它的类型,又要调用它,这就是两次读取
      then = x.then; //because x.then could be a getter
      if (typeof then === "function") {
        // 如果
        then.call(
          x,
          function rs(y) {
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            return resolvePromise(promise2, y, resolve, reject);
          },
          function rj(r) {
            if (thenCalledOrThrow) return;
            thenCalledOrThrow = true;
            return reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (thenCalledOrThrow) return;
      thenCalledOrThrow = true;
      return reject(e);
    }
  } else {
    resolve(x);
  }
}

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this;
  var promise2;
  // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(v) {
          return v;
        };
  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(r) {
          throw r;
        };

  if (self.status === "resolved") {
    return (promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        // 异步执行onResolved
        try {
          var x = onResolved(self.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.status === "rejected") {
    return (promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        // 异步执行onRejected
        try {
          var x = onRejected(self.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.status === "pending") {
    // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
    return (promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.onRejectedCallback.push(function(reason) {
        try {
          var x = onRejected(reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });
    }));
  }
};

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
};

# 为什么 Promise.then 返回的是一个异步函数

仔细观察上面的代码,我们发在在 then 函数中return new Promise里面使用了setTimeout;个人理解其主要的原因是为了保证代码执行顺序的一致性;

假设有这么一段代码:

let cache = null;
function getValue() {
  if (cache) {
    return Promise.resolve(cache); // 存在cache,这里为同步调用
  }
  return fetch("/api/xxx").then((r) => (cache = r)); // 这里为异步调用
}
console.log("before getValue");
getValue.then(() => console.log("getValue"));
console.log("after getValue");

如果在 Promise.then 返回的是同步的情况,在有缓存的情况下(cache!=null)程序调用结果如下:

before getValue

getValue

after getValue

当缓存未被 resolved 的情况下,返回 pending promise, then 为异步,打印如下:

before getValue
after getValue
getValue

当 getValue 的缓存机制作为内部实现,接口作为黑盒提供给外部调用时,调用方完全无法判断内部到底是同步还是异步,对程序的可维护性和可测性带来极大影响。如果都统一设计为异步,则永远只会有一种确定的情况,从可维护和可测性角度来看都无疑是最佳实践。

# all 和 race 和 finally 的实现

all 和 race 两个函数都是并发执行 promise 的方法,他们的返回值也是 promise,all 会等所有的 promise 都决议之后决议,而 race 是只要有一个决议就会决议。

Promise.all = function(promises) {
  let results = new Array(promises.length);
  let promiseCount = 0;
  let promisesLength = promises.length;
  return new Promise(function(resolve, reject) {
    for (let index = 0; index < promises.length; index++) {
      const ele = promises[index];
      (function(index) {
        Promise.resolve(ele).then(
          function(res) {
            results[index] = res;
            promiseCount++;
            if (promiseCount === promisesLength) {
              return resolve(results);
            }
          },
          function(err) {
            return reject(err);
          }
        );
      })(index);
    }
  });
};

Promise.race = function(arr) {
  return new Promise(function(resolve, reject) {
    if (!Array.isArray(arr)) {
      return reject(new TypeError("Promise.race accepts an array"));
    }

    for (var i = 0, len = arr.length; i < len; i++) {
      // 谁的异步时间段就处理谁
      Promise.resolve(arr[i]).then(resolve, reject);
    }
  });
};

Promise.resolve = function(value) {
  if (value && typeof value === "object" && value.constructor === Promise) {
    return value;
  }

  return new Promise(function(resolve) {
    resolve(value);
  });
};

Promise.reject = function(value) {
  return new Promise(function(resolve, reject) {
    reject(value);
  });
};

Promise.prototype.finally =
  Promise.prototype.finally ||
  {
    finally(fn) {
      const onFinally = (callback) => Promise.resolve(fn()).then(callback);
      return this.then(
        (result) => onFinally(() => result),
        (reason) => onFinally(() => Promise.reject(reason))
      );
    },
  }.finally;

# 如何停止一个 Promise 链?

参考:从如何停掉 Promise 链说起 (opens new window)

# 用 Promise 封装一下原生 ajax

function ajaxPromise(url, method, data, async, timeout) {
  var xhr = new XMLHttpRequest();
  return new Promise(function(resolve, reject) {
    xhr.open(method, url, async);
    xhr.timeout = options.timeout;
    xhr.onloadend = function() {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)
        resolve(xhr);
      else
        reject({
          errorType: "status_error",
          xhr: xhr,
        });
    };
    xhr.send(data);
    //错误处理
    xhr.onabort = function() {
      reject(
        new Error({
          errorType: "abort_error",
          xhr: xhr,
        })
      );
    };
    xhr.ontimeout = function() {
      reject({
        errorType: "timeout_error",
        xhr: xhr,
      });
    };
    xhr.onerror = function() {
      reject({
        errorType: "onerror",
        xhr: xhr,
      });
    };
  });
}
【未经作者允许禁止转载】 Last Updated: 2/4/2024, 6:06:40 AM