promise实现求助
发布于 10 个月前 作者 huhu2356 998 次浏览 来自 问答

萌新按照剖析 Promise 之基础篇学习实现了下promise,但是发现有一个很大的问题,那就是then在promise已改变状态下注册方法,该注册方法不会异步执行,变成了立即执行,请教下各位老哥,代码该如何修改(handle方法),还是说其他方法有问题,亦或是萌新我的理解有误。。。

代码如下:

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;

function SimplePromise(fn) {
  let state = PENDING;
  let value = null;
  let deferreds = [];

  this.then = function (onFulfilled, onRejected) {
    return new SimplePromise (function (resolve, reject) {
      // 当promise某一结果状态处理函数完成后,应将bridge promise状态改变,并向其传递结果值
      handle({
        onFulfilled: onFulfilled || null,
        onRejected: onRejected || null,
        resolve: resolve,
        reject: reject
      });
    });
  }

  function handle(deferred) {
    if (state === PENDING) {
      deferreds.push(deferred);
      return;
    }

    let cb = state === FULFILLED ? deferred.onFulfilled : deferred.onRejected;
    // 当前promise一结果状态未注册相应处理函数,则将状态传递
    if (cb === null) {
      cb = state === FULFILLED ? deferred.resolve : deferred.reject;
      cb(value);
      return;
    }

    try {
      let ret = cb(value);
      deferred.resolve(ret);
    } catch (e) {
      deferred.reject(e);
    }
  }

  function finale() {
    setTimeout(function () {
      deferreds.forEach(deferred => handle(deferred));
    }, 0);
  }

  function resolve(newValue) {
    // 用于处理方法返回的结果是promise
    if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
      let then = newValue.then;
      if (typeof then === 'function') {
        // bridge promise为了获取方法中返回的promise的结果值,向它注册异步操作后的处理函数
        then.call(newValue, resolve, reject);
        return;
      }
    }

    state = FULFILLED;
    value = newValue;
    finale();
  }

  function reject(reason) {
    state = REJECTED;
    value = reson;
    finale();
  }

  function doResolve(fn, resolve, reject) {
    let done = false;
    try {
      fn(function (value) {
        if (done) return;
        done = true;
        resolve(value);
      }, function (reson) {
        if (done) return;
        done = true;
        reject(reson);
      })
    } catch (e) {
      if (done) return;
      done = true;
      reject(e);
    }
  }

  doResolve(fn, resolve, reject);
}

比如在这种情况下会出现问题: 按规范不是应该先打印aaa吗?


function getUserId() {
  return new SimplePromise(function (resolve) {
    resolve(9876);   
  });
}

getUserId()
  .then(function (mobile) {
      console.log('do sth with', mobile);
      return 'ccc';
  })
  .then(console.log);

console.log('aaa');

打印结果:


do sth with 9876
ccc
aaa

3 回复

你看看它实现的Resolve,reject方法

都是同步运行的,所以当然不符合规范了

只要在Promise初始化的时候,添加异步就好了

function doResolve(fn, resolve, reject) {
    let done = false;
    try {
      fn(
        function(value) {
          if (done) return;
          done = true;
		  // 这里加上
          setTimeout(function () {
            resolve(value);
          })
        },
        function(reson) {
          if (done) return;
          done = true;
		  // 这里加上
          setTimeout(function () {
            reject(reson);
          })
        }
      );
    } catch (e) {
      if (done) return;
      done = true;
	  // 这里加上
      setTimeout(function () {
        reject(e)
      });
    }
  }

测试一下

function getUserId() {
  return new SimplePromise(function(resolve) {
    console.log(0);
    resolve(1);
  });
}

getUserId()
  .then(function(num) {
    console.log(num);
    return Promise.resolve(2);
  })
  .then(function (num) {
    console.log(num);
    return Promise.resolve(3);
  })
  .then(function (num) {
    console.log(num)
  });

console.log("aaa");

// 0
// aaa
// 1
// 2
// 3

@axetroy

嗯嗯 这样好像可以了,但我在想能不能resolve,reject让其同步执行,但是实际注册的onFulfilled,onRejected方法异步执行,比如说在handle方法里加个setTimeout;

  function handle(deferred) {
    if (state === PENDING) {
      deferreds.push(deferred);
      return;
    }

    let cb = state === FULFILLED ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      cb = state === FULFILLED ? deferred.resolve : deferred.reject;
      cb(value);
      return;
    }
    // 这里加个setTimeout
    setTimeout(function() {
      try {
        let ret = cb(value);
        deferred.resolve(ret);
      } catch (e) {
        deferred.reject(e);
      }
    }, 0);
  }

运行结果和你的那个例子相同,不知道这样加会不会在其他某种情况下出问题?

@axetroy

大佬,我又想了下按照我之前的思路(promise的状态是同步改变,但是让其注册处理方法异步执行),如果把handle里的方法异步执行用process.nextTick()替换setTimeout()是否会更好?

比如说: promise状态处于pending,对其多次调用then,即对同一promise注册多个方法,一旦该promise状态改变,则执行deferreds数组里之前注册的所有对应方法;finale()中调用deferreds里的每个元素时采用setTimeout(),我若要让其该方法在被调用后立即执行,应使用process.nextTick(); 如果再一次调用setTimeout()来执行,在会将该方法放置到timers phase所在queue的队尾。

但就结果而言,这两种方式都会按照then注册方法时候的顺序执行。那么这两种方式在promise实现的情况下是否会有区别,亦或是否正确?

对这部分问题提取了下:

let funs = [];

funs.push(() => console.log(1));
funs.push(() => console.log(2));
funs.push(() => console.log(3));

setTimeout(() => {
  funs.forEach(fun => {
    setTimeout(fun, 0);
  })
}, 0);

// 或者用process.netxTick()
setTimeout(() => {
  funs.forEach(fun => {
    process.nextTick(fun);
  })
}, 0);

我又仔细想了下,then的注册方法不一定会按顺序执行:

只要能精确的控制timers队列的任务插入时机,就会让后注册的处理方法先执行(包括大佬你的改动也会出现问题)

以下是按照我的想法实现出现问题情况:

function getSimplePromise() {
  return new SimplePromise(function(resolve) {
    setTimeout(() => resolve(1), 2);
  });
}

let pp = getSimplePromise();

pp.then(num => console.log('first', num));

pp.then(num => console.log('second', num));

pp.then(num => console.log('third', num));

setTimeout(() => pp.then(() => console.log('haha')), 2);

console.log("aaa");

// aaa
// haha
// first 1
// second 1
// third 1

大佬你的能避免出现以上这种情况,但我如果修改条件为(如下),同样会出现问题

function getSimplePromise() {
  return new SimplePromise(function(resolve) {
    setTimeout(() => resolve(1), 2);
  });
}

let pp = getSimplePromise();

pp.then(num => console.log('first', num));

pp.then(num => console.log('second', num));

pp.then(num => console.log('third', num));

// setTimeout(() => pp.then(() => console.log('haha')), 2);

setTimeout(function () {
  setTimeout(() => pp.then(() => console.log('haha')), 0);
}, 2);

console.log("aaa");

// aaa
// haha
// first 1
// second 1
// third 1

又隔一天,哈哈哈 对于timer queue 中callbacks的push时机应该是在交由poll phase来处理,实际执行则是timer queue中;想明白了这点后,让其单个注册方法异步执行,代码改为: (希望这次改动不会出现问题,哈哈哈哈)

  function handle(deferred) {
	// ....bala bala 这部分代码和之前的一样

	// 异步执行单个注册方法
    setTimeout(function() {
      try {
        let ret = cb(value);
        deferred.resolve(ret);
      } catch (e) {
        deferred.reject(e);
      }
    }, 0);
      
  }

  function finale() {
  	 // 这里就应该在promise状态改变后立刻对这个数组进行处理,将其中的所有注册方法压入(这里用的是setTimeout)timers queue中
      deferreds.forEach(deferred => handle(deferred));
  }
回到顶部