Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS-ES6-Promise/Promise A+规范 #112

Open
yaofly2012 opened this issue Feb 21, 2020 · 5 comments
Open

JS-ES6-Promise/Promise A+规范 #112

yaofly2012 opened this issue Feb 21, 2020 · 5 comments

Comments

@yaofly2012
Copy link
Owner

yaofly2012 commented Feb 21, 2020

Promise A+规范

一、概述

Promise A+规范重点分为3个部分:

  1. Promise的状态
  2. then方法的功能和注册的回调函数调用规则;
  3. The Promise Resolution Procedure

1.1 Promise的状态

  1. pending
  2. fulfilled
  3. rejected

初始态和终态

pending状态是Promise对象的初始状态,
fulfilled/rejected状态是Promise的终态,一个Promise对象的终态二选一。

最终值(value/reason)

Promise是一个异步操作的结果,当promise处于终态时会关联一个最终值

  • fulfilled promise对应一个最终结果value
  • rejected promise对应一个最终结果reason

1.2 then方法

  1. then方法是必须的
    无法直接访问Promise对象的终态值,必须通过then方法注册回调函数的方式获取Promise的最终值(注意这是唯一的方式):
promise.then(onFulfilled, onRejected)
  1. onFulfilled/onRejected必须异步调用

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack

Promise对象链

then方法返回值是个新Promise对象,这样就构成一个Promise对象链。

1.3 The Promise Resolution Procedure

Promise解决过程主要是描述一个Promise对象如果变成终态的。

[[Resolve]](promise, x)

基本的原则是:

  1. 如果xthenable对象,则采用x最终值;
  2. 否则直接fulfill promise对象。

二、thenable对象

2.1 什么是thenable对象

Promise A+标准中定义:

“thenable” is an object or function that defines a then method.

  1. Promise对象也是thenable对象,但是thenable对象不一定是Promise对象。
  2. thenable对象为了兼容非标准的Promise规范实现。

2.2 在[[Resolve]](promise, x)中如果xthenable对象

Promises/A+规范吧:把Promise对象的resolvereject句柄作为实参调用then方法。

var thenable = {
    then: function(resolve, reject) {
        console.log('thenable.then');
        resolve();
    }
}
console.log(1)
Promise.resolve(thenable).then(() => {
    console.log('fulfilled')
}, () => {
    console.log('rejected')
})

console.log(2)

输出:

1
2
thenable.then
fulfilled

注意:
在ES2015 Promise实现中thenable对象的then方法调用是异步的,作为微任务下一个时序执行(后面再细说)。

三、关于Promise A+规范疑惑点

  1. then方法返回值promise2不可以和该then方法的onFulfilled或则onRejected回调函数的返回值result相同。
    因为如果resultpromise2相等(相等的promise),则产生跟自身互相引用的局面,永远无法进入终态。

  2. JS-ES6-Promise.prototype.then链的误区

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 21, 2020

一、ES2015 Promise

1.1 概述

  1. ES2015 Promise函数是对PromiseA+标准的实现,并且严格遵守该标准。

A promise represents the eventual result of an asynchronous operation.

  1. Promise本质还是callback的异步编程,只不过把如何进行使用callback进行规范化, 并按照采用统一的接口来编写,这样更有利理解,维护。解决了[callback hell]问题。

1.2 PromiseRejectionEvent事件

当出现rejected promise时,全局对象会触发MDN PromiseRejectionEvent事件,PromiseRejectionEvent只是个接口,具体有两个实现:

1. unhandledrejection事件

如果rejected promise没有添加onRejected处理函数,则触发unhandledrejection事件;

 window.onunhandledrejection = function (e) {
    console.log(e.reason)
}

Promise.reject({ api: 'webview.push', message: 'not support'})

2. rejectionhandled事件

如果rejected promise最初未被处理,但是稍后又得到了处理(即触发时机和onRejected处理函数绑定不在同一个EventLoop里),则触发rejectionhandled事件。

window.onrejectionhandled = function (e) {

                    console.log(e.reason)
                }

var rp3 = Promise.reject({ api: 'webview.open', message: 'not support'})
// 注意:这里是在下一个EventLoop才添加onRejected事件处理函数
                setTimeout(function () {
                    rp3.then(null, function(reason) {
                        debugger
                        console.log(reason)
                    })
                }, 0)
  • 只有在下一个EventLoop里添加onRejected事件处理函数的rejected Promise,才会触发rejectionhandled事件;
window.onrejectionhandled = function (e) {

                    console.log(e.reason)
                }

Promise.reject({ api: 'webview.open', message: 'not support'})
.then(null, function(reason) {
                        debugger
                        console.log(reason)
                    })

这里并不会触发rejectionhandled事件。

  • 触发rejectionhandled事件一定会触发unhandledrejection事件;
  • rejectionhandledunhandledrejection事件都是全局触发的。

二、APIs

2.1 创建Promise对象

什么是Promise对象?Promise A+标准说道

A promise represents the eventual result of an asynchronous operation

即Promise是一个异步操作的最终结果。

ES6通过构造函数Promise创建Promise对象:

new Promise( function(resolve, reject) {...} /* executor */  )

构造函数的实参函数是用来调用“异步操作”的,所以实参会在构造函数调用时同步执行(经常出现在脑筋急转弯的练习里)。
实参函数执行逻辑遵循的就是Promise A+ thenable.then的规范:
image

2.2 Promise.prototype.catch

then方法的别名,等价于Promise.prototype.then(undefined, onRejected)

asynOperation().catch(function(reason){
})
// 等价于
asynOperation().then(undefined, function(reason){
})

2.3 Promise.prototype.finally

无论Promise处于什么终态都会触发onFinally回调函数,但是finally方法并不等价于.then(onFinally, onFinally)

Promise.reject('failed')
.finally(() => {
    console.log('handle in finally')
})
.then(value => {
    console.log(`Fullfilled: handle in then value=${value}`)
}, reason => {
    console.log(`Rejected: handle in then reason=${reason}`)
})

输出:

handle in finally
Rejected: handle in then reason=failed

改成then(onFinally, onFinally) 效果:

Promise.reject('failed')
.then(() => { // 这里替换`finally`
    console.log('handle in finally')
}, () => {
    console.log('handle in finally')
})
.then(value => {
    console.log(`Fullfilled: handle in then value=${value}`)
}, reason => {
    console.log(`Rejected: handle in then reason=${reason}`)
})

输出:

handle in finally
Fullfilled: handle in then value=undefined

  1. finally的回调函数没有参数,因为它不知道Promise的终态是fullfilled还是rejected;
  2. finally()方法的原则是不改变then链的执行过程(除非finally方法内部抛出异常),所以看到上面的例子中finally方法就像不存在前后then链里一样。
Promise.reject('failed')
.finally(() => {
    console.log('handle in finally')
    return 'Hi i am finally'; // 显示的返回个value,但是返回的Promise对象依旧是rejected
})
.then(value => {
    console.log(`Fullfilled: handle in then value=${value}`)
}, reason => {
    console.log(`Rejected: handle in then reason=${reason}`) // 
})
  1. 如果finally的回调函数返回的是个Promise,则也是等这个Promise对象进入终态后才执行后面的then链。
Promise.reject('failed')
.finally(() => {
    console.log('handle in finally')
    console.log('waiting 3s ...')
    return new Promise(resolve => { // 3s后再执行后面的
        setTimeout(resolve, 3000) // 虽然fullfilled,但是finally方法返回的Promise对象依旧rejected
    })
})
.then(value => {
    console.log(`Fullfilled: handle in then value=${value}`)
}, reason => {
    console.log(`Rejected: handle in then reason=${reason}`)
})
  1. 如果finally的回调函数抛出异常,或则返回个rejected的Promise,则会替换前面Promise的终态value或则reason
Promise.reject('failed')
.finally(() => {
    console.log('handle in finally')
    console.log('waiting 3s ...')
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('reason from finally callback ')
        }, 3000)
    })
})
.then(value => {
    console.log(`Fullfilled: handle in then value=${value}`)
}, reason => {
    console.log(`Rejected: handle in then reason=${reason}`)
})

输出:

handle in finally
waiting 3s ...
Rejected: handle in then reason=reason from finally callback

  1. finally的行为好奇葩,最后还是把它写在then链的最后,以防阅读代码障碍。
  2. finally方法遵循的原则就是透传上一个Promise对象的结果,但是如果发生错误,则告诉后面的then链最新的错误。

2.3 Promise.resolve(value)

  1. 返回个处于“fullfilled"状态的Promise对象;
  2. 如果实参value是个thenable对象(不包含Promise对象),则会进行The Promise Resolution Procedure,即此时Promise.resolve(v)并等价于new Promise(resolve => resolve(v))
    并且Promise的状态不一定是fulfilled
var thenable = {
    then: function(resolve, reject) {
        console.log('thenable.then');
        reject();
    }
}
var p = new Promise(resolve => resolve(thenable))
p.then(() => {
    console.log('fulfilled')
}, () => {
    console.log('rejected')
})

输出:

thenable.then
rejected

  1. 如果实参value是个Promise对象(不包含thenable对象),则直接返回实参;
var p = new Promise(() => {});
var p1 = Promise.resolve(p)
console.log(p1 === p) // true

正因为Promise.resolve具有这个判断逻辑,所以当遇到不确定目标对象是否为Promise对象但又需要是个Promise对象的场景时,可以利用Promise.resolve包裹下目标对象。

此时 Promise.resolve(v)并不等价于new Promise(resolve => resolve(v))
之前一直以为是等价的,直到看到这个令人费解的 async/await 执行顺序

new Promise(resolve => {
    resolve();
})
.then(() => {
    console.log(1)
})

Promise.resolve()
.then(() => {
    console.log(2)
})

输出结果:1 -> 2,把value改成个Promise对象试试:

var p = new Promise(resolve => {
    resolve();
});

new Promise(resolve => {
    resolve(p);
})
.then(() => {
    console.log(1)
})

Promise.resolve(p)
.then(() => {
    console.log(2)
})

输出结果居然变了。

综上:
只有当实参value不是个Promise对象时,则Promise.resolve(v)等价于new Promise(resolve => resolve(v))

2.4 Promise.reject(reason)

  1. 返回个处于“Rejected”状态的Promise对象。
  2. Promise.resolve不同的是Promise.reject(reason)等价于new Promise(function(resolve, reject){ reject(reason);})

2.5 Promise.all(iterable)

2.5.1 介绍

Promise构造函数的静态函数,参数是Promise对象组成的可迭代数据集合。创建个Promise对象,并且参数指定的所以Promise都解决后,该Promise对象该被解决,反之如果其中存在rejected的Promise,则该Promise对象被rejected。
定义个异步操作:

function asynOperation(value, reason){
	var promise = new Promise(function(resolve, reject){
		setTimeout(function(){
			value === undefined ? reject(reason) : resolve(value);
		}, 3000)
	});
	return promise;
	
}

var p1 = asynOperation(1),
	p2 = asynOperation(2),
	p3 = asynOperation(3);
Promise.all([p1, p2, p3]).then(function(value){
	console.log('all resolved: value is ' + value); // value是[1, 2, 3] 
})

如果参数元素中发生rejected操作,则立马reject返回的Promise:

var p1 = asynOperation(1),
	p2 = asynOperation(undefined, 2), // reject操作
	p3 = asynOperation(undefined, 3); // reject操作
p2.catch(function(reson){
	console.log('p2')
})
p3.catch(function(reson){
	console.log('p3')
})
Promise.all([p1, p2, p3]).then(function(value){
	console.log('all resolved: value is ' + value)
}, function(reason){
	console.log('one rejected: reason is ' + reason); // reson值等于发生reject操作的promise对象的reason,即p2
})

显示结果:

2.5.2 对比$.when

Promise.all和$.when很类似。主题功能差不多,参数传递方式不一样:ES2015中把所有的Promise对象的value构建个数组,而$.when是分别传递的。

function AsynOperation1(value, reason){
	var deferred = $.Deferred();
	setTimeout(function(){
		value === undefined ? deferred.reject(reason) : deferred.resolve(value);
	}, 3000);
	return deferred.promise();
}
var p1 = AsynOperation1(1),
	p2 = AsynOperation1(2),
	p3 = AsynOperation1(3);
$.when(p1, p2, p3).then(function(value){
	console.log('resolved ' + value); // 留意Value的值
}, function(reason){
	console.log('rejected ' + reason);
})

2.6 Promise.race(iterable)

Promise构造函数的静态函数,参数是Promise对象构成的可迭代对象,创建个Promise对象。当参数任意Promise对象fullfilled(rejected)时,则立马fullfill(reject)该Promise对象。

var p1 = asynOperation(1),
	p2 = asynOperation(undefined, 2),
	p3 = asynOperation(undefined, 3);
Promise.race([p1, p2, p3]).then(function(value){
	console.log('one resolved: value is ' + value); // Value=1
}, function(reason){
	console.log('one rejected: reason is ' + reason)
})

2.7 总结

Promise A+规范只定义了then方法,E2015中Promise API提供了更多了方法(Promise.prototype.catch, Promise.prototype.finally, Promise.race, Promise.all)这些都是基于then的扩展方法,方便开发使用。

三、micro-task

Promise的回调通过micor-task实现的。

console.log(1)
setTimeout(function(){ console.log(2)}, 0)
Promise.resolve().then(function(){console.log(3)})
console.log(4)
// 输出应该是:1 4 3 2

四、标准 Promise 的不足

4.1 不能中断,没有 abort、terminate、onTimeout 或 cancel 方法

一旦发起,不能中断,也不会超时,只能等待被 resolve 或 reject。幸运的是,whatwg 目前正在尝试解决这个问题whatwg/fetch#27

42 【鸡蛋里挑骨头】没有获取状态方法:isRejected,isResolved

标准 Promise 没有提供获取当前状态 rejected 或者 resolved 的方法。只允许外部传入成功或失败后的回调。我认为这其实是优点,这是一种声明式的接口,更简单。

4.3 【鸡蛋里挑骨头】缺少其它一些常用方法:always,finally

always 可以通过在 then 和 catch 里重复调用方法实现。finally 也类似。
不过这个也已经处于草案了MDN finally

4.4 【鸡蛋里挑骨头】没有 Deferred

Deferred 可以在创建 Promise 时可以减少一层嵌套,还有就是跨方法使用时很方便。
ECMAScript 11 年就有过Deferred 提案,但后来没被接受。其实用 Promise 不到十行代码就能实现 Deferred:es6-deferred。现在有了 async/await,generator/yield 后,deferred 就没有使用价值了。

参考

  1. 是时候使用promise了
  2. JavaScript Promise迷你书(中文版)
  3. MDN
  4. Javascript Promise All Example | Promise.All() In JS
  5. 捕获未处理的Promise错误

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 22, 2020

Promise Resolve函数

一、概述

Promise构造函数的实参函数和thenable对象的then函数在被调用时第一个实参是resolve函数。
resolve(value)的执行过程就是The Promise Resolution Procedure

二、执行逻辑

ES6的实现逻辑完全符合The Promise Resolution Procedure`,但此外还有些额外的逻辑:

[[Resolve]](promise, x)
  1. 如果 x是个Promise对象(或者thenable对象),则会增加一个转换实参为Promise的行为(即使实参本身就是个Promise对象),并且这个转换过程是异步的任务,相当于后面的then方法回调被延迟了一个时序。
    这个过程叫PromiseResolveThenableJob
  2. Promise.prototype.then方法的onFullfilledonRejected回调函数如果返回个Promise对象,也存在同样的问题。

2.1 x是Promise对象

var p = Promise.resolve('p');
var p1 = new Promise(resolve => {    
    resolve(p); // 会进行PromiseResolveThenableJob
})

p1
.then(() => {
    console.log('p1.1')
})

p
.then(() => {
    console.log('p.1')
})
.then(() => {
    console.log('p.2')
})
.then(() => {
    console.log('p.3')
})

输出:

p.1
p.2
p1.1
p.3

现象:
p1会被延时两个时序才被fulfilled

分析:

  • PromiseResolveThenableJob作为微任务下个时序执行(延时一个时序);
  • 在执行PromiseResolveThenableJob时,为了获取p的value,需要通过p.then方法注册onFulfilled回调函数(相当于又延时一个时序)。

2.2 x是thenable对象

var thenable = {
    then: function(resolve) {
        console.log('thenable.then');
        resolve();
    }
}

var p = Promise.resolve('p');
var p1 = new Promise(resolve => {    
    resolve(thenable)
})

p1
.then(() => {
    console.log('p1.1')
})

p
.then(() => {
    console.log('p.1')
})
.then(() => {
    console.log('p.2')
})
.then(() => {
    console.log('p.3')
})

输出:

thenable.then
p.1
p1.1
p.2
p.3

现象:p1会被延时一个时序才被fulfilled

分析:

  • PromiseResolveThenableJob作为微任务下个时序执行(延时一个时序);
  • 在执行PromiseResolveThenableJob时,p1会被fulfilled

2.3 reject句柄函数

reject句柄存不在上面的问题,直接把实参(即使是Promise对象,thenable对象)作为rejectedreason;
Promise A+规范里关于thenable处理这样描述的

If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
If/when rejectPromise is called with a reason r, reject promise with r.

var p = Promise.resolve('p');
var p1 = new Promise((resolve, reject) => {    
    reject(p)
})

// [[PromiseState]]: "rejected"
// [[PromiseResult]]: Promise
console.log(p1); 

参考

  1. 令人费解的 async/await 执行顺序

@yaofly2012 yaofly2012 changed the title JS-ES6-Promse/Prmose A+规范 JS-ES6-Promise/Promise A+规范 May 22, 2020
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 10, 2020

一道关于Promise应用的面试题

题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)

为了方便演示,增加额外的条件:只需要循环3次。

1. Promise最常规实现

function red () {
    console.log('red')
}

function green () {
    console.log('green')
}

function yellow() {
    console.log('yellow')
}

var tic = function(timmer, cb){
    return new Promise(function(resolve, reject) {
        cb();
        setTimeout(resolve, timmer);
    });
}

var round = 0;
var maxRound = 3;
;(function step() {
    if(++round > maxRound ) {
        return;
    }
    console.log(`Round ${round}`)
    tic(3000, red)
    .then(function() {
        return tic(2000, green)
    })
    .then(function() {
        return tic(1000, yellow)
    })
    .then(step)
})()

2. 使用 Array.prototype.reduce方法优化调用方式

题目本质就是元素前后存在依赖的数组,可以采用Array.prototype.reduce方法优化调用方式。

var round = 0;
var maxRound = 3;
;(function step(){
     if(++round > maxRound) {
        return;
    }
    console.log(`Round ${round}`);
    [red, green, yellow, step].reduce((prev, cb, index, arr) => {
        return prev.then(() => {
            return tic(1000 * (arr.length - index - 1), cb)
        })
    }, Promise.resolve())
})()

3. 基于Generator函数的实现

var round = 0;
var maxRound = 3;

function* gen() {    
    yield tic(3000,red);
    yield tic(2000, green);
    yield tic(1000, yellow)
}
(function step() {
    if(++round > maxRound) {
        return;
    }
    console.log(`Round ${round}`);
    
    var g = gen();
    (function next(){
        var result = g.next();
        if(result.done) {
           step();
        } else {
            result.value.then(next)
        }
    })()    
})();
  1. 果然使用Gengerator函数控制异步流程确实比较烧脑,还是async/await

4. 基于async/await的实现

var round = 0;
var maxRound = 3;

(async function step() { 
   if(++round > maxRound) {
        return;
    }
    console.log(`Round ${round}`);
    await tic(3000,red);
    await tic(2000, green);
    await tic(1000, yellow)
    step();
})()
  1. 代码清晰,可读性强,异步操作还得看async/await

2.5 基于callback实现

主要是看评论区里有人提供了callback方案,想着跟Expressjs的中间件函数调用类似,自己也写个callback方式的:

function red (next) {
    console.log('red')
    setTimeout(next, 3000)
}

function green (next) {
    console.log('green')
    setTimeout(next, 2000)
}

function yellow(next) {
    console.log('yellow')
    setTimeout(next, 1000)
}

var round = 0;
var maxRound = 3;
;(function step() {
    if(++round > maxRound ) {
        return;
    }
    console.log(`Round ${round}`)

    var tasks = [red, green, yellow, step]
    ;(function next() {
        var task = tasks.shift();
        task(next);       
    })()
})()

@yaofly2012 yaofly2012 added the TODO label Mar 9, 2021
@yaofly2012
Copy link
Owner Author

更多练习

1. 实现Promise.race(iterable);

Promise.race = function(promiseList ) {
    return new Promise((resolve, reject) => {
        (promiseList || []).forEach(promiseItem => {
            // 利用Promise.resolve功能判断promiseItem是否为Promise对象
            Promise.resolve(promiseItem).then(resolve, reject);
        })
    })
}

保证Promise.race的三点功能

  • 返回值是个Promise对象;
  • 实参数组为空或则为假值时,返回值一致处于pending状态;
  • 实参数组的元素是非Promise对象时,直接作为fulfilledPromise对象的value

2. 实现Promise.all(iterable)

Promise.all = function(promiseList) {
    promiseList = promiseList || [];
    var valueList = [];
    var length = promiseList.length;
    var resolvedCount = 0;

    return new Promise((resolve, reject) => {
        promiseList.forEach((promiseItem, index) => {
            ;(function(index) {
                Promise.resolve(promiseItem).then((value) => {
                    valueList[index] = value;
                    if(length === ++resolvedCount) {
                        resolve(valueList)
                    }
                }, reject)
            })(index)
        })
    })   
}

Promise.all的实现容易出错的几点可参考Promise.all和Promise.race源码实现

  1. fulfilledPromise对象计数问题;

3. 实现Promise.prototype.finally(onFinally)

Promise.prototype.finally = function(onFinally) {
    
    return this.then(value => {
        return Promise.resolve(onFinally())
            .then(() => value, // 透传上个Promise对象的终态`value`
            reason => {
                throw reason // 如果拒绝,则使用新的`reason`
            });
        }, reason => {
           return Promise.resolve(onFinally())
               .then(() => {
                   throw reason // 透传上个Promise对象的终态`reason`
               }, reason => {
                    throw reason // 如果拒绝,则使用新的`reason`
                });
        })
}

看了Promise.prototype.finally/finally.js库实现后发现,自己写的又啰嗦又不严谨:

Promise.prototype['finally ']= function(onFinally) {
    
    return this.then(value => {
        return Promise.resolve(onFinally())
            .then(() => value); // 透传上个Promise对象的终态`value`           
        }, reason => {
           return Promise.resolve(onFinally())
               .then(() => {
                   throw reason // 透传上个Promise对象的终态`reason`
               });
        })
}
  1. finally 是关键词,考虑到ES3环境性还是用中括号的方式定义吧;
  2. onRejected回调函数可以不用显示的再写;
  3. 总之记住finally的原则:
  • 透传上个Promise对象fulfilledvalue
  • 反馈最新的错误(即如果finally发生了异常或则rejected则会使用新的reason传给后后的then链)。

4. 输出结果分析

new Promise(resolve => {
    resolve ();
})
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))

new Promise(resolve => {
    resolve ();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))
  1. 复习Promise A+规范 then方法
  2. 复习Promise A+规范 the-promise-resolution-procedure

5. 输出结果分析

let thenable = {
  then(resolve, reject) {
    console.log('in thenable'); // 3
    resolve(100);
  }
};

new Promise((r) => {
  console.log('in p0'); // 1
  r(thenable);
})
.then(() => { console.log('thenable ok') }) // 5

new Promise((r) => {
  console.log('in p1'); // 2
  r();
})
.then(() => { console.log('1') }) // 4
.then(() => { console.log('2') })
.then(() => { console.log('3') })
.then(() => { console.log('4') });

@yaofly2012 yaofly2012 removed the TODO label Mar 12, 2021
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 23, 2022

如何实现一个Promise请求池?

源于思否上的一个提问:如何实现一个Promise请求池?
自己也写个:

function PromisePool(maxSize) {
    const queue = [];
    let activeCount = 0;

    function push(action) {
        // 返回个promise对象,方便后续使用(比如结合`Promise.all`等)
        return new Promise((resolve, reject) => {
            queue.push(async () => {
                try {
                    const result = await action();
                    resolve(result);                    
                } catch (e) {
                    reject(e);
                } finally {
                    --activeCount;
                    run(); // 调用run, 看看还有没有pending的action需要执行
                }
              }
            );
            // 调用run, 开始执行
            run();
        })
    }

    function run() {
        if (activeCount < maxSize && queue.length) {
            activeCount++;
            const action = queue.shift();
            action();
        }
    }

    return push;
}

// TEST
var pp = PromisePool(2);

function genP(msg, delay) {
    return new Promise(resolve=>{
        setTimeout(()=>{
            console.log(msg);
            resolve(msg);
        }
        , delay)
    }
    )
}

Promise.all([
    pp(()=>genP('a', 500)),
    pp(()=>genP('b', 100)),
    pp(()=>genP('c', 50)),
    pp(()=>genP('d', 80))
]).then(result => {    
    console.log(result)
})

参考

  1. p-limit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant