Daily-Interview-Question icon indicating copy to clipboard operation
Daily-Interview-Question copied to clipboard

第 64 题:模拟实现一个 Promise.finally

Open lvtraveler opened this issue 6 years ago • 30 comments

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

lvtraveler avatar Apr 28 '19 01:04 lvtraveler

window.Promise && !('finally' in Promise) && !function() {        
  Promise.prototype.finally = function(cb) {
    cb = typeof cb === 'function' ? cb : function() {};
      
    var Fn = this.constructor;  // 获取当前实例构造函数的引用

    // 接受状态:返回数据
    var onFulfilled = function(data) {
      return Fn.resolve(cb()).then(function() {
        return data
      })
    };

    // 拒绝状态:抛出错误
    var onRejected = function(err) {
      return Fn.resolve(cb()).then(function() {
        throw err
      })
    };

    return this.then(onFulfilled, onRejected);
  }
}();

/*********************** 测试 ***********************/
const p = new Promise((resolve, reject) => {
  console.info('starting...');

  setTimeout(() => {
    Math.random() > 0.5 ? resolve('success') : reject('fail');
  }, 1000);
});

// 正常顺序测试
p.then((data) => {
    console.log(`%c resolve: ${data}`, 'color: green')
  })
  .catch((err) => {
    console.log(`%c catch: ${err}`, 'color: red')
  })
  .finally(() => {
    console.info('finally: completed')
  });

// finally 前置测试  
p.finally(() => {
    console.info('finally: completed')
  })	
  .then((data) => {
    console.log(`%c resolve: ${data}`, 'color: green')
  })
  .catch((err) => {
    console.log(`%c catch: ${err}`, 'color: red')
  });

wingmeng avatar Apr 28 '19 07:04 wingmeng

	window.Promise.prototype = {
			finally: function(callback) {
				let P = this.constructor;
				  return this.then(
				    value  => P.resolve(callback()).then(() => value),
				    reason => P.resolve(callback()).then(() => { throw reason })
				  );
			}
		}

抄了个...

xbcc123 avatar May 03 '19 02:05 xbcc123

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => {
          callback();
          return value;
     },
    reason => {
        callback();
        throw reason
    }
  );
};

为什么需要Promise.resolve(callback()).then(() => value) 而不能直接执行callback, return value

hello-chinese avatar Jun 12 '19 06:06 hello-chinese

Promise.prototype.finally = function(callback) {
        return this.then(
          () => {
            callback();
          },
          () => {
            callback();
          }
        );
      };

NathanHan1 avatar Jul 16 '19 03:07 NathanHan1

Promise.prototype.finally = function(callback){
  const constructor = this.constructor
  return this.then(value => {
    return constructor.resolve(callback()).then(() => value)
  }),
  reason => {
    return constructor.resolve(callback()).then(() => throw reason)
  }
}

Arrogant128 avatar Jul 17 '19 09:07 Arrogant128

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => {
          callback();
          return value;
     },
    reason => {
        callback();
        throw reason
    }
  );
};

为什么需要Promise.resolve(callback()).then(() => value) 而不能直接执行callback, return value

因为callback如果是个异步操作,返回promise呢.希望等callback执行完再接着执行

HCLQ avatar Jul 19 '19 02:07 HCLQ

https://github.com/taylorhakes/promise-polyfill/blob/master/src/finally.js

finally是一个关键字,在IE低版本会引发解析错误,若兼容IE不能直接object.key语法.

Promise.prototype['finally'] = function (callback) {
  var constructor = this.constructor;
  return this.then(
    function(value) {
      // @ts-ignore
      return constructor.resolve(callback()).then(function() {
        return value;
      });
    },
    function(reason) {
      // @ts-ignore
      return constructor.resolve(callback()).then(function() {
        // @ts-ignore
        return constructor.reject(reason);
      });
    }
  );
}

pagemarks avatar Aug 16 '19 01:08 pagemarks

看了这个问题才知道,原来 promise 的 finally 只是不管成功还是失败都会执行而已,而不会永远最后执行😂

new Promise((resolve, reject) => {
    resolve();
}).finally(() => {
    console.log('finally1');
}).then(() => {
    console.log('then');
}).finally(() => {
    console.log('finally2');
});
finally1
then
finally2

yft avatar Aug 26 '19 02:08 yft

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

想问下,这里先通过P缓存this.constructor,后面再通过P.resolve的方式调用是有什么目的么,为什么不是直接value => Promise.resolve(callback()).then(() => value),reason => Promise.resolve(callback()).then(() => { throw reason })~ 谢谢

x-shadow-x avatar Sep 25 '19 01:09 x-shadow-x

来个简洁版的:

finally(callback) {
    return this.then(
        (res) => {
            callback();
            return res;
        },
        (err) => {
            callback();
            return err;
        }
    );
}

nianxiongdi avatar Oct 06 '19 09:10 nianxiongdi

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

看一个多月,还是没看懂,哪位大神能逐句解释一下

SoftwareEngineerPalace avatar Oct 14 '19 06:10 SoftwareEngineerPalace

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

想问下,这里先通过P缓存this.constructor,后面再通过P.resolve的方式调用是有什么目的么,为什么不是直接value => Promise.resolve(callback()).then(() => value),reason => Promise.resolve(callback()).then(() => { throw reason })~ 谢谢

同问

SoftwareEngineerPalace avatar Oct 14 '19 06:10 SoftwareEngineerPalace

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

想问下,这里先通过P缓存this.constructor,后面再通过P.resolve的方式调用是有什么目的么,为什么不是直接value => Promise.resolve(callback()).then(() => value),reason => Promise.resolve(callback()).then(() => { throw reason })~ 谢谢

我觉得是为了兼容那些自定义的Promise版本。比如说可以自己实现一个Promise叫MyPromise,像resolve和reject这些静态方法都是挂载在MyPromise上的。通过这样let P = this.constructor;写,兼容性更好吧。

liujie2019 avatar Nov 05 '19 12:11 liujie2019

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value, () => value),
    reason => P.resolve(callback()).then(() => { throw reason }, () => {throw reason})
  );
};

如上在阮大神关于 finally 的写法中补充了一点。不知是否正确,欢迎探讨和学习。

callback 可能存在返回 promise,而该 promise 如果 reject,P.resolve 就会 reject,如果 P.resolve().then() 没有设置第二个回调,那么 this.then 的最终状态将是 reject 的状态,这与 es6 中所表现出来的 finally 的行为不一致。

如下是 es6 所表现的行为:

new Promise((resolve, reject) => resolve(111)).finally(Promise.reject(222)) // 此时 es6 表现的行为状态为 resolve(111),而非 reject(222)

webfool avatar Nov 24 '19 08:11 webfool

@webfool 新加的代码与规范中规定的行为不符合, 你的代码:

new Promise((resolve, reject) => resolve(111))
  .finally(Promise.reject(222)) // Promise {<resolved>: 111}

之所以最终返回的 Promise的状态为 resolve(111), 那是因为你传给finally函数的参数的不是函数类型, 而是一个Promise对象.

如果对代码进行小小修改, 就发现 @lvtraveler 实现的 finally方法与 es6 的 Promisefinally 的行为一致:

new Promise((resolve, reject) => resolve(111))
  .finally(() => Promise.reject(222)) // Promise {<rejected>: 222}

现在再解释为什么你的代码得到Promise {<resolved>: 111}的结果, 即代码是如何执行的.

规范中对这种传给finally函数的参数的不是函数类型情况的处理如下:

Promise.prototype.finally = function(onFinally) {
  if (typeof onFinally !== 'function') {
    return this.then(onFinally, onFinally)
 }
  // ...
}

那么this.then(onFinally, onFinally)又是怎么处理的呢?

对于在 then方法中传入非函数类型的参数的情况, Promise/A+规范 2.2.1.1 和 2.2.1.2规定如下:

A promise’s then method accepts two arguments: promise.then(onFulfilled, onRejected)

  • 2.2.1 Both onFulfilled and onRejected are optional arguments:
    • 2.2.1.1 If onFulfilled is not a function, it must be ignored.
    • 2.2.1.2 If onRejected is not a function, it must be ignored.

注: 这里没有给出 ES6 规范中对于 Promise 的规定, 是因为ES6 的 Promise 行为遵循 Promise/A+ 规范, 另外我也没看过 ES6 的规范

所以代码最后相当于:

Promise.prototype.finally = function(onFinally) {
  if (typeof onFinally !== 'function') {
    return this.then()  // 注: bluebird 也是这么实现的: https://github.com/petkaantonov/bluebird/blob/master/src/finally.js#L93
 }
  // ...
}

对于 onFulfilled and onRejected 为非函数类型的情况, 具体的执行行为规范2.2.7.3 和 2.2.7.4 规定如下:

promise2 = promise1.then(onFulfilled, onRejected);

  • If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
  • If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

根据对规范的理解, 具体的执行行为用代码表示为:

Promise.prototype.finally = function(onFinally) {
  if (typeof onFinally !== 'function') {
    return this.then(
      val => val,
      reason => {
        throw reason
      }
    )
  }
// ...
}

再回到最初你的代码, 就知道最终finally返回的 Promise的状态由new Promise.resolve(111) 这个Promise来决定

thxiami avatar Jan 02 '20 11:01 thxiami

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

看一个多月,还是没看懂,哪位大神能逐句解释一下

第一步获取构建函数,这里constructor就是获取,为什么用这个呢?主要是以前的promise实现有第三方的存在 后面返回一个then对象,then接受两个方法,这里统一用resolve接收,之后等待callback执行完成后继续返回结果和抛出异常

bosens-China avatar Feb 13 '20 11:02 bosens-China

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

看一个多月,还是没看懂,哪位大神能逐句解释一下

首先是生成函数,这里构造函数就是获取,为什么用这个呢呢?主要是以前的promise实现有第三方的存在 后面返回一个对象,然后接受两个方法,这里统一用resolve接收,之后等待回调执行完成后继续返回结果和抛出异常

有一点不明白,为什么异常处理要写在then的第二个参数里,而不是用catch方法

Mrcxt avatar May 11 '20 08:05 Mrcxt

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => {
          callback();
          return value;
     },
    reason => {
        callback();
        throw reason
    }
  );
};

为什么需要Promise.resolve(callback()).then(() => value)

而不能直接执行callback, return value

这里都不需要有 let P = this.constructor; 这一句了吧

cutie6 avatar May 22 '20 09:05 cutie6

Promise._finally = function(cb) {
  return this.then(value => { cb(); return value }, err => { cb(); throw err})
}

rottenpen avatar Jun 01 '20 13:06 rottenpen

Promise.prototype.myFinally = function (func) { return this.then(func) } 直接这样就好了呀

Fan-zexu avatar Jul 08 '20 10:07 Fan-zexu

PromiseA.prototype.finally = (fn) => {
	// 保证了fn执行在前.. 但是有点绕
	return this.then((param)=>{
		// 万一 fn reject了
		return PromiseA.resolve(fn()).then(()=>param, ()=>param);
	}, (err) =>{
		// 万一 fn reject了
		return PromiseA.resolve(fn()).then(()=>{
			throw err;
		}, ()=>{
			throw err;
		});
	})
}

tjwyz avatar Jul 16 '20 12:07 tjwyz

  1. finally 方法 , 不管 Promise 对象最后状态如何,都会执行的操作, 返回的依然是个Promise;
  2. 参数 cb 没有接收之前 Promise 的值, 只是执行 cb 并继承之前的状态
  finally (cb) {
    return this.then(
      value  => Promise.resolve(cb()).then(() => value),
      reason => Promise.resolve(cb()).then(() => { throw reason })
    );
  }

m7yue avatar Sep 10 '20 15:09 m7yue

	window.Promise.prototype = {
			finally: function(callback) {
				let P = this.constructor;
				  return this.then(
				    value  => P.resolve(callback()).then(() => value),
				    reason => P.resolve(callback()).then(() => { throw reason })
				  );
			}
		}

抄了个...

你这样是不是把prototype都覆盖了

Eleven-Ding avatar Aug 22 '21 11:08 Eleven-Ding

Promise.prototype.myFinally = async function (cb) {
  const pr = this;
  try {
    await pr;
  } finally {
    cb && cb();
  }
};

const start = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const temp = Math.round(Math.random());
      if (temp > 0.5) {
        resolve(temp);
      } else {
        reject(temp);
      }
    }, 2000);
  });
};

start()
  .then((res) => {
    console.log("res", res);
  })
  .catch((err) => {
    console.log("err", err);
  })
  .myFinally(() => {
    console.log("finally");
  });

更多解析

jackluson avatar Aug 24 '21 09:08 jackluson

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => {
          callback();
          return value;
     },
    reason => {
        callback();
        throw reason
    }
  );
};

为什么需要Promise.resolve(callback()).then(() => value) 而不能直接执行callback, return value 你这个不能处理异步情况

XMH19970729 avatar Dec 15 '21 06:12 XMH19970729

看了这个问题才知道,原来 promise 的 finally 只是不管成功还是失败都会执行而已,而不会永远最后执行joy

new Promise((resolve, reject) => {
    resolve();
}).finally(() => {
    console.log('finally1');
}).then(() => {
    console.log('then');
}).finally(() => {
    console.log('finally2');
});
finally1
then
finally2

你不说,我都没仔细注意到。。。

SnailOwO avatar Dec 20 '21 08:12 SnailOwO


Promise.prototype.finally = function (callback) {
    return this.then(
        r => Promise.resolve(callback()).then(() => r),
        e => Promise.resolve(callback()).then(() => { throw e })
    )
}

Yangfan2016 avatar Aug 23 '22 12:08 Yangfan2016

看了这个问题才知道,原来 promise 的 finally 只是不管成功还是失败都会执行而已,而不会永远最后执行😂

new Promise((resolve, reject) => {
    resolve();
}).finally(() => {
    console.log('finally1');
}).then(() => {
    console.log('then');
}).finally(() => {
    console.log('finally2');
});
finally1
then
finally2

你不说,我都没注意到,招你这样说,这就是个普通函数而已

wangsen2020 avatar Oct 19 '22 10:10 wangsen2020

这样应该没什么问题吧,直接this也不用管是引入的还是默认的,cb也简单处理了一下,也能传递数据或者错误

Promise.prototype.finally = function(cb) {
    cb = typeof cb === 'function' ? cb : () => cb
    return this.then(cb, cb)
}

xuxin666666 avatar Oct 23 '22 15:10 xuxin666666