实现 JS promise

https://promisesaplus.com/
需求包括:

  1. 同一个promise允许被多次then/catch
  2. then/catch函数: then(onFulfilled, onRejected), catch(onRjecte)
    • then/catch 返回一个新的promise,因此允许链式调用
    • onFulfilled/onReject为null时, 继续将当前result/error往之后的chain传
    • 每个onFulfilled/onReject最多被执行一次
    • onFulfilled/onReject执行过程中抛出异常时,立即往之后的chain抛异常
    • 链尾的异常不应该被之前的promise catch到
    • catch之后被恢复: 执行onRjected的返回值作为result

实现要点:

  1. 如何让同一个promise多次then/catch: 每个promise维护一个defer callbacks队列,每次then时将callback加入队列就好了
  2. 如何返回新的promise: 将传给then的onFulfilled/onReject改造成v => next_resolve(onFulfilled(v)), e => next_resolve(onRjected(e))
  3. 注意try/catch细节,和处理下onFulfilled/onReject可能返回promise的情况
let isThenable = x => !!(x!==undefined && x.then);
let runThenable = (func, arg) => isThenable(arg) ? arg.then(func) : func(arg);

class MyPromise{
 constructor(f){
   this.succ_que = [];
   this.fail_que = [];
   this.done = false;

   this.resolve = result => {
     if(this.done) return;
     this.result = result;
     this.done = true;
     setImmediate(() => {          // setImmediate to prevent children from being caught by parent
       this.succ_que.forEach(cb => cb(result));
     });
   };

   this.reject = error => {
     if(this.done) return;
     this.error = error;
     this.done = true;
     setImmediate(() => {
       this.fail_que.forEach(cb => cb(error));
     });
   };

   this.then = (succ_cb, fail_cb) => new MyPromise((next_resolve, next_reject) => {
     let handle_result = v => {
       try{
         runThenable(next_resolve, succ_cb ? succ_cb(v) : v);    // runThenable to allow succ_cb/fail_cb return a promise
       } catch (e) {
         next_reject(e)
       }
     };
     let handle_error = e => {
       try{
         if(fail_cb)
           runThenable(next_resolve, fail_cb(e));    // resume after caught by fail_cb
         else
           next_reject(e);
       }catch (e) {
         next_reject(e)
       }
     };

     if(this.done){
       this.error ? handle_error(this.error) : handle_result(this.result)
     }else{
       this.succ_que.push(handle_result);
       this.fail_que.push(handle_error);
     }
   });

   this.catch = fail_cb => this.then(null, fail_cb);

   try{
     f(this.resolve, this.reject);
   }catch (e) {
     this.reject(e);
   }
 }
}

MyPromise.resolve = x => new MyPromise(r => r(x));
MyPromise.Race = (...promises) => {
 let done = 0;
 return new MyPromise(r => {
   promises.forEach(p => p.then(v =>{ if(!done){done++; r(v);} }));
 })
};
MyPromise.All = (...promises)=>{
 let count = promises.length;
 let values = [];

 return new MyPromise((r, f) => {
   promises.forEach((p, i) =>
     p.then(v => {
       values[i] = v;
       count --;
       if(count === 0) r(values);
     }, f)
   )
 });
};
// tests:
// then/catch on the same promise:
let defer = new MyPromise(r => setImmediate(()=>r(100)));
[1,2,3].forEach(plus => defer.then(v => console.log(v+plus)));    // 101, 102, 103

let defer_error = new MyPromise((_, rej) => setImmediate(()=>rej(new Error("defer error"))));
[1,2,3].forEach(i => defer_error.catch(e => console.log(i, e.message)));

// chained then/catch:
let p = MyPromise.resolve(1);
p.then(v=> v+1)
  .catch(e => console.log(`won't happen error: ${e}`))
  .then(v => {console.log(`continued: ${v}`); throw new Error("throw");})
  .then(v => {console.log("won't happen then");})
  .catch(e => {console.log(`catched: ${e}`); return 100;})
  .then(v => {console.log(`continue after catch: ${v}`); return v;})
  .then(v => new MyPromise(r=> setTimeout(() => r(v+500), 3000)))
  .then(v => console.log(`last: ${v}`))
;
console.log("===========");

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272