在JavaScript中实现promise类

z9gpfhce  于 2023-02-07  发布在  Java
关注(0)|答案(3)|浏览(167)

我尝试用javascript实现一个简单的promise类,它具有可链接的.then()功能。

class APromise {
    constructor(Fn) {
        this.value = null;
        Fn(resolved => { this.value = resolved; });
    }
    then(fn) {
        fn(this.value);
        return this;
    }
}

function myFun() {
    return new APromise(resolve => {
        // just resolve('Hello'); works but this doesn't
        setTimeout(() => { resolve('Hello'); }, 2000);
    });
}

const log = v => { console.log(v); };

myFun().then(log).then(log);

它输出-

null
null

而不是'Hello' 2次。我认为它目前正在忽略setTimeout()调用,我应该如何使此工作?

bfnvny8b

bfnvny8b1#

问题是

您的代码没有按您希望的方式工作,因为您将异步流与同步流混合在一起。
当你调用.then()时,它会同步返回this,因为setTimeout()是一个异步函数,在一段时间(2秒)后调用,所以this.value仍然是null
如果你想了解更多关于JS的异步流程,我推荐你看this video,它有点长,但是非常有帮助。

让您的代码工作

由于我们无法知道setTimeout()何时调用传递给它的函数,因此我们无法调用和回调依赖于它的操作的函数,我们将这些回调存储在一个数组中以供以后使用。
setTimeout()函数被调用(promise resolves)时,我们得到了promise resolves的结果,因此,我们现在调用所有绑定的回调函数。

class APromise {
    constructor(Fn) {
        this.value = null;
-       Fn(resolved => { this.value = resolved; });
+       this.callbacks = [];
+       Fn(resolved => {
+           this.value = resolved;
+
+           this.callbacks.forEach(cb => {
+               cb(this.value);
+           });
+       });
    }
    then(fn) {
-       fn(this.value);
+       this.callbacks.push(fn);
        return this;
    }
}

function myFun() {
    return new APromise(resolve => {
        setTimeout(() => { resolve('Hello'); }, 2000);
    });
}

const log = v => { console.log(v); };

myFun().then(log).then(log);

链接问题

上面的代码部分地解决了这个问题。
当一个回调函数的结果传递给下一个回调函数时,就实现了真正的链接。在我们当前的代码中,情况并非如此。要实现这一点,每个.then(cb)必须返回一个新的APromise,该APromisecb函数被调用时解析。
一个完整且符合Promises/A+的实现远远超出了单个SO答案的范围,但这不应该给人一种它不可行的印象。

富勒实现

让我们从头开始,我们需要一个类Promise,它实现了一个方法then,该方法也返回一个允许链接的承诺。

class Promise {
    constructor(main) {
        // ...
    }

    then(cb) {
        // ...
    }
}

在这里,main是一个函数,它 * 将一个函数作为参数 *,并在承诺得到解决/实现时调用它-我们将此方法称为resolve()。上述函数resolve()由我们的Promise类实现和提供。

function main(resolve) {
    // ...
    resolve(/* resolve value */);
}

then()方法的基本特性是,一旦承诺实现,就用承诺值触发/激活提供的回调函数cb()
考虑到这两点,我们可以重新连接Promise类。

class Promise {
    constructor(main) {
        this.value = undefined;
        this.callbacks = [];

        const resolve = resolveValue => {
            this.value = resolveValue;

            this.triggerCallbacks();
        };

        main(resolve);
    }

    then(cb) {
        this.callbacks.push(cb);
    }

    triggerCallbacks() {
        this.callbacks.forEach(cb => {
            cb(this.value);
        });
    }
}

我们可以使用tester()函数测试当前代码。

(function tester() {
    const p = new Promise(resolve => {
        setTimeout(() => resolve(123), 1000);
    });

    const p1 = p.then(x => console.log(x));
    const p2 = p.then(x => setTimeout(() => console.log(x), 1000));
})();

// 123 <delayed by 1 second>
// 123 <delayed by 1 more second>

这就结束了我们的基础。现在我们可以实现链接。我们面临的最大问题是then()方法必须返回一个承诺同步,而这个承诺将被异步解决。
我们需要等待解析 * parent promise *,然后才能解析 * next promise *。这意味着我们必须添加 * next promise * 的resolve()方法,而不是将cb()添加到 * parent promise *,该方法使用cb()的返回值作为其resolveValue

then(cb) {
-   this.callbacks.push(cb);
+   const next = new Promise(resolve => {
+       this.callbacks.push(x => resolve(cb(x)));
+   });
+
+   return next;
}

如果这最后一点让你感到困惑,这里有一些提示:

  • Promise构造函数接受函数main()作为参数
  • main()将函数resolve()作为参数
  • resolve()Promise构造函数提供
  • resolve()采用 * any * 类型的参数作为resolveValue

演示

class Promise {
    constructor(main) {
        this.value = undefined;
        this.callbacks = [];

        const resolve = resolveValue => {
            this.value = resolveValue;

            this.triggerCallbacks();
        };

        main(resolve);
    }

    then(cb) {
        const next = new Promise(resolve => {
            this.callbacks.push(x => resolve(cb(x)));
        });

        return next;
    }

    triggerCallbacks() {
        this.callbacks.forEach(cb => {
            cb(this.value);
        });
    }
}

(function tester() {
    const p = new Promise(resolve => {
        setTimeout(() => resolve(123), 1000);
    });

    const p1 = p.then(x => console.log(x));
    const p2 = p.then(x => setTimeout(() => console.log(x), 1000));
    const p3 = p2.then(x => setTimeout(() => console.log(x), 100));
    const p4 = p.then((x) => new Promise(resolve => {
        setTimeout(() => resolve(x), 1000);
    }))

    /*
        p: resolve after (1s) with resolveValue = 123
        p1: resolve after (0s) after p resolved with resolveValue = undefined
        p2: resolve after (0s) after p resolved with resolveValue = timeoutID
        p3: resolve after (0s) after p2 resolved with resolveValue = timeoutID
        p4: resolve after (1s) after p resolved with resolveValue = Promise instance
    */
})();

// 123  <delayed by 1s>
// 2    <delayed by 1.1s>
// 123  <delayed by 2s>
r1wp621o

r1wp621o2#

在Promise中调用同步时解决此问题:

class MyPromise{
    constructor(fn){
        this.callback= null;
        this.data= null;
        this.calledInNext= false;
        fn((data, state)=>{ // unsafe when call resolve({}, 'string')
            this.calledInNext= (state === 'CALLED_IN_NEXT') ? true : false;
            this.data= data;
            if(this.callback) this.callback(this.data);
        }, function(_err){
            console.log('resolve({error}) to without catch')
        })
    }
    then(cb){ // next
        if(this.data || this.calledInNext){
            return new MyPromise(r => {
                r(cb(this.data), 'CALLED_IN_NEXT');
            });
        } else {
            return new MyPromise(r => {
                this.callback = x=> r(cb(x))
            })       
        }
    }
}

或链条:

class MyPromise{
    constructor(fn){
        this.callbacks= [];
        this.data= null;
        fn((data)=>{
            this.data= data;
            var gg= this.data;
            this.callbacks.forEach(el=>{
                gg= el(gg);
            })
        })
    }
    then(cb){
        if(this.data || this._calledInNext){
            this._calledInNext= true; this.data= cb(this.data); return this;
        } else {
            this.callbacks.push(cb); return this;
        }
    }
}

试验:

(new MyPromise(function(resolve, reject){
    // setTimeout(resolve, 1000, {done: 1})
    resolve({done: 1})
})).then(data=>{
    console.log(data);      // {done: 1}
    return data;
}).then(data=>{
    console.log(data);      // {done: 1}
    return {};
}).then(data=>{
    console.log(data);      // {}
}).then(data=>{
    console.log(data);      // undefine
}).then(data=>{
    console.log(data);      // undefine
}).then(data=>{
    console.log(data);      // undefine
})
mftmpeh8

mftmpeh83#

我自己写了一个简单的承诺

class Promise {
  constructor(main) {
    this.main = main;
    this.mainExecuted = false;
    this.resolved = false;
    this.rejected = false;
    this.promiseChain = [];
    this.handleError = () => {};
    this.onResolve = this.onResolve.bind(this);
    this.onReject = this.onReject.bind(this);
  }

  then(handleSuccess) {    
    if (this.resolved) {
        if(!this.rejected) {
        this.args = handleSuccess(this.args)
      }
    }
    else {
      this.promiseChain.push(handleSuccess);
      this.main(this.onResolve, this.onReject);
      this.thenExecuted = true;
    }
    return this;
  }

  catch(handleError) {
    this.handleError = handleError;
    if(!this.mainExecuted) {
            this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
    }
    return this;
  }

  onResolve(...args) {
    this.resolved = true;
    this.args = args;
        try {
      this.promiseChain.forEach((nextFunction) => {
        this.args = nextFunction(...this.args);
      });
    } catch (error) {
      this.promiseChain = [];

      this.onReject(error);
    }
  }

  onReject(error) {
    this.rejected = true;
    this.handleError(error);
  }
}

为旧的僵尸浏览器(ES3以上版本)实现

我使用的Promise发现在旧的浏览器中这个类是不存在的,所以我为他们实现了一个有resolvereject方法的类,这个方法是用babel传递的:

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
var Promise = /*#__PURE__*/function () {
  "use strict";

  function Promise(main) {
    _classCallCheck(this, Promise);
    this.main = main;
    this.mainExecuted = false;
    this.resolved = false;
    this.rejected = false;
    this.promiseChain = [];
    this.handleError = function () {};
    this.onResolve = this.onResolve.bind(this);
    this.onReject = this.onReject.bind(this);
  }
  _createClass(Promise, [{
    key: "then",
    value: function then(handleSuccess) {
      if (this.resolved) {
        if (!this.rejected) {
          this.args = handleSuccess(this.args);
        }
      } else {
        this.promiseChain.push(handleSuccess);
        this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
      }
      return this;
    }
  }, {
    key: "catch",
    value: function _catch(handleError) {
      this.handleError = handleError;
      if (!this.mainExecuted) {
        this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
      }
      return this;
    }
  }, {
    key: "onResolve",
    value: function onResolve() {
      var _this = this;
      this.resolved = true;
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      this.args = args;
      try {
        this.promiseChain.forEach(function (nextFunction) {
          _this.args = nextFunction.apply(void 0, _toConsumableArray(_this.args));
        });
      } catch (error) {
        this.promiseChain = [];
        this.onReject(error);
      }
    }
  }, {
    key: "onReject",
    value: function onReject(error) {
      this.rejected = true;
      this.handleError(error);
    }
  }]);
  return Promise;
}();

相关问题