前言
我们用了前面两个文章的篇幅给出了 Promise 及其相关 API 的完整实现。在实际的业务场景(当然包括大家很关心的面试环节)中有很多是基于 Promise 的实现方式,本篇将给出一部分常见问题的代码解答。
1、常见场景
1.1、前菜 timeout,给爱加一个期限
正所谓 “一万年太久只争朝夕”,Promise 生来就无法打断,假如你正好碰上了一个非常耗时的任务,这个时候你需要给他一期限。
这个场景里,一般是需要你为你的承诺——Promise加上一个期限(即,添加原型方法 setTimeout)。
那么简单分析,setTimeout 方法需要满足两个条件:
- 它需要返回一个新的 promise 方法,以便继续链式调用
- 拿到此前的承诺状态,此前承诺如果在期限内兑现(能够达到终态,在一起/分手)就传递状态,超期则强制达到终态(分手)
Ok,直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| MyPromise.prototype.timeout = function(ms) { return new MyPromise((resolve, reject) => { this.then(res => { resolve(res) }) .catch(err => { reject(err) }) setTimeout(() => { reject(new Error('timeout')) }, ms) }) }
|
1.2、限流调度器
限流调度器的应用场景很多,常见比如:网络请求中进行并发控制。
1.2.1、场景1
考虑这么一个场景,有一个调度器 Scheduler 类。它通过一个 add 函数可以不断的往里面添加异步任务(实现上可以是一个返回 Promise 对象的函数)。并且控制并发度(限流)为 m,即同时可执行 m 个异步任务。
初始代码框架
1 2 3 4 5 6 7 8 9
| class Scheduler { constructor(){ } async add(promiseFunc){ return new Promise((resolve, reject) => { }) } }
|
该场景下,添加任务的时候判断下是否达到最大并发度,然后再决定是进行任务执行还是加入等待队列。当执行中的任务执行完毕后唤起等待任务执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Scheduler { constructor(){ this.waitTasks = [] this.runningCount = 0 this.taskMax = 2 } async add(promiseFunc){ return new Promise((resolve, reject) => { const task = this.getTask(promiseFunc, resolve, reject) if(this.runningCount < this.taskMax) { task() }else{ this.waitTasks.push(task) } }) } getTask(fn, resolve, reject) { return () => { this.runningCount++ fn() .then(resolve, reject) .finally(()=>{ this.runningCount-- if(this.waitTasks.length > 0) { const task = this.waitTasks.shift() if(task) task() } }) } } }
|
1.2.2、场景2
该场景下,会议开始接受一组任务,然后设定并发度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| function batch(tasks, concurrency) { let finishCount = 0 const len = tasks.length function sealingTask(fn, resolve, idx, ans){ return ()=>{ fn() .then(res=>{ ans[idx] = { status:'fulfilled', value: res } }, err=>{ ans[idx] = { status:'rejected', reason: err } }) .finally(()=>{ finishCount++ if(finishCount === len){ resolve(ans) return } const task = tasks.shift() if(task){ sealingTask(task, resolve, len - tasks.length - 1, ans)() } }) } } return new Promise((resolve, reject)=>{ if(len) { const ans = new Array(len) for (let i = 0; i < len && runningCount < concurrency; i++) { const task = tasks.shift() sealingTask(task, resolve, i, ans)() } }else{ resolve([]) } }) }
|
代码层面并不复杂,一上来直接填满任务进行执行。每个任务执行的时候要注意执行结果在 ans 中的顺序。任务执行完要进行下一个任务唤起,并检查是否已经完成所有任务结果并修改 promise 状态。
1.3、控制器
代码实现一个控制器,使得调用后在控制台打印这样的结果
1 2 3 4 5 6 7 8 9 10 11 12
| function SingleDog(name) //TODO } SingleDog('Hank').sleep(2).eat('dinner').sleep(1) 控制台: Hi! This is Hank! Wake up after 2 Eat dinner~ Wake up after 1
|
仔细看这个题,首先,要实现一个链式调用。SingleDog 应该返回一个对象,这个对象有 sleep、eat 方法。其次要实现定时控制。那么我们首先有一个 Executor 函数,这个函数有两个原型方法,这两个方法返回 this 对象以实现链式调用。并且在 SingleDog 中返回一个 Executor 的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function SingleDog(name){ console.log(`Hi! This is ${name}!`) return new Executor() } function Executor(){ } Executor.prototype.sleep = function(seconds){ return this } Executor.prototype.eat = function(food){ return this }
|
Ok,代码实现到这里,有两个思路,一个是通过 sleep、eat 方法收集任务,然后把收集到的任务按类型进行处理执行。但是在我们这里肯定要用 Promise 来实现。
看一下需求,eat比较简单,那么就变成了怎么来实现 sleep 进行定时控制。
我们考虑下一般的 Promise 实现链式调用代码:
1 2 3 4 5 6 7 8 9 10 11
| new Promise(resolve=>{ resolve(1) }) .then(rs=>{ console.log('then1') }) .then(rs=>{ console.log('then2') })
|
假如,我想把 then1 的打印做一个延迟打印可以这么修改
1 2 3 4 5 6 7 8 9 10 11
| new Promise(resolve=>{ resolve(1) }) .then(rs=>{ setTimeout(()=>console.log('then1'), 1000) }) .then(rs=>{ console.log('then2') })
|
那 then2 的打印放在 then1 后面呢?我们回顾下之前的内容,then 方法返回的是一个新的 promise 对象,这个对象的终态会被 then2 执行掉。如果 then1 的 onfulfilled 方法返回的是一个 promise A 呢?那么 then2 的 onfulfilled 方法将在 promise A 达到终态后执行。再次修改代码后如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| new Promise(resolve=>{ resolve(1) }) .then(rs=>{ return new Promise(resolve=>{ setTimeout(()=>{ console.log('then1') resolve() }, 1000) }) }) .then(rs=>{ console.log('then2') })
|
Ok,现在我们来拆解下。最开始 Executor 中只有初始化 promise 对象,then1 部分就是 sleep 函数,而 then2 部分就是 eat 函数。接下来把代码填装一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function Executor(){ this.promise = Promise.resolve() } Executor.prototype.sleep = function(seconds){ this.promise = this.promise.then(()=>{ return new Promise(resolve=>{ setTimeout(()=>{ console.log(`Wake up after ${seconds}`) resolve() }, seconds * 1000) }) }) return this } Executor.prototype.eat = function(food){ this.promise = this.promise.then(()=>{ console.log(`Eat ${food}~`) }) return this }
|
2、结语
Ok,Promise 系列至此已经结束。希望通过这三篇的学习能够让大家对 Promise 有更加深入的认识。第三篇的目的并不是让大家去有目的的记一些题,而是在对 Promise 的实现机制有更加深刻的理解,能够在实际的业务场景中有所发挥。此外,鉴于本人水平有限,代码或表述中有问题可以联系我进行交流。
附录
1.3 小节中控制器的非 Promise 实现参数代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| function Task(type, arg, executor) { this.executor = executor this.type = type this.arg = arg } Task.prototype.run = function(){ if(this.type === 'eat'){ console.log(`Eat ${this.arg}~`) this.executor.refresh() this.executor.next() }else{ setTimeout(() => { console.log(`Wake up after ${this.arg}`) this.executor.refresh() this.executor.next() }, this.arg * 1000) } } function Executor(){ this.queue = [] this.running = false } Executor.prototype.sleep = function(delay){ this.queue.push(new Task('sleep', delay, this)) this.next() return this } Executor.prototype.eat = function(food){ this.queue.push(new Task('eat', food, this)) this.next() return this } Executor.prototype.next = function(){ if(this.running) return let task = this.queue.shift() if(task){ this.running = true task.run() } } Executor.prototype.refresh = function(){ this.running = false }
|