sisterAn / JavaScript-Algorithms

基础理论+JS框架应用+实践,从0到1构建整个前端算法体系

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

阿里异步串行编程题:按照以下要求,实现 createFlow 函数

sisterAn opened this issue · comments

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);

createFlow([
  () => console.log("a"),
  () => console.log("b"),
  subFlow,
  [() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
  console.log("done");
});

// 需要按照 a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印

按照上面的测试用例,实现 createFlow

  • flow 是指一系列 effects 组成的逻辑片段。
  • flow 支持嵌套。
  • effects 的执行只需要支持串行。

在本题中, createFlow 以一个数组作为参数,数组参数可有以下几种类型:

  • 普通函数
() => console.log("a")
  • 异步函数
() => delay(1000).then(() => console.log("c"))
  • 嵌套 createFlow
const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))])
  • 数组
[() => delay(1000).then(() => console.log("d")), () => console.log("e")]

步骤一:扁平化

因为 effects 的执行只需要支持串行,所以我们可以把数组扁平化一下

function createFlow(effects = []) {
  // 浅拷贝一下,今年不影响传入的参数
  const queue = [...effects.flat()]
}

步骤二:run执行

createFlow 并不是直接执行,而是 .run() 之后才会开始执行

function createFlow(effects = []) {
  const queue = [...effects.flat()]
  const run = async function(cb) {
    for(let task of queue) {
    }
    if(cb) cb()
  }
}

步骤三:异步函数

因为参数中有异步函数,这里使用 await 执行

function createFlow(effects = []) {
  const queue = [...effects.flat()]
  const run = async function(cb) {
    for(let task of queue) {
      await task()
    }
    if(cb) cb()
  }
}

步骤四:支持嵌套

使用 isFlow 来判断当前是否是嵌套执行

function createFlow(effects = []) {
  const queue = [...effects.flat()]
  const run = async function(cb) {
    for(let task of queue) {
      if(task.isFlow) { // 如果是嵌套,执行嵌套函数
        await task.run()
      } else {
        await task()
      }
    }
    if(cb) cb()
  }
  return {
    run,
    isFlow: true
  }
}

其实是可以使用 class 来实现,用 instanceof 来判断是否是嵌套,代码实现如下:

class Flow {
  constructor(props) {
    this.queue = props
  }
  async run(cb) {
    for(let task of this.queue) {
      if(task instanceof Flow) {
        await task.run()
      } else {
        await task()
      }
    }
    if(cb) cb()
  }
}

function createFlow(effects = []) {
    return new Flow(effects)
}

完整代码:两种

  • function
function createFlow(effects = []) {
  const queue = [...effects.flat()]
  const run = async function(cb) {
    for(let task of queue) {
      if(task.isFlow) {
        await task.run()
      } else {
        await task()
      }
    }
    if(cb) cb()
  }
  return {
    run,
    isFlow: true
  }
}

// 测试
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);

createFlow([
  () => console.log("a"),
  () => console.log("b"),
  subFlow,
  [() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
  console.log("done");
});

// a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印
  • class
class Flow {
  constructor(effects) {
    this.queue = [...effects.flat()]
  }
  async run(cb) {
    for(let task of this.queue) {
      if(task instanceof Flow) {
        await task.run()
      } else {
        await task()
      }
    }
    if(cb) cb()
  }
};

function createFlow(effects = []) {
    return new Flow(effects)
};

// 测试
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);

createFlow([
  () => console.log("a"),
  () => console.log("b"),
  subFlow,
  [() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
  console.log("done");
});
// a,b,延迟1秒,c,延迟1秒,d,e, done 的顺序打印

参考:https://juejin.im/post/6860646761392930830

commented
  const createFlow = (lists) => {
    return new Promise((re, rj) => {
      let i=0
      let len = lists.length
      
      const next = (i)=>{
        if(i == lists.length) return re()

        let item = lists[i]
        if(Array.isArray(item)) {
          createFlow(item).then(() => next(i+1))
        }else if(item instanceof Promise) {
          Promise.resolve(item).then(() => next(i+1))
        }else if(typeof item == 'function') {
          let res = item()
          if(res instanceof Promise){
            Promise.resolve(res).then(() =>next(i+1))
          }else{
            next(i+1)
          }
        }else {
          next(i+1)
        }
      }

      next(i)
    })
  }
  Promise.prototype.run = function(cb) {
    this.then(cb)
  }

  const delay = ms => new Promise(re => setTimeout(re, ms))
  const subFlow = createFlow([() => delay(1000).then(() => console.log('c'))])
  createFlow([
    () => console.log('a'),
    () => console.log('b'),
    subFlow,
    [() => delay(1000).then(() => console.log('d')),  () => console.log('e')],
  ]).run(() => console.log('done'))
commented

之前会错意了,思路一样,利用 Promise 异步串行

  const createFlow = (lists) => {
    let tasks = [...lists].flat()
      
    const run = (cb) => {
      let [first, ...others] = [...tasks]
      return others.reduce((promise, task) => {
        task = task.run ? task.run : task  
        return Promise.resolve(promise).then(_ => task())
      }, first()).then(cb)
    }
    return { run }
  }

其实我还有一个思路:

  • 收集参数部分相同;

  • 在 run 的时候,判断 task.then 是否是可执行的方法,请看代码

function isType(target, type) {
  return (Object.prototype.toString.call(target) || '').indexOf(type) > -1;
}

function log(msg) {
  return console.log(msg);
}

function createFlow(arr) {
  let task = [];
  arr.forEach(item => {
    if (isType(item, 'Function')) {
      if (item.name === 'createFlow') {
        task = task.concat(item.task);
      } else {
        task.push(item);
      }
    }
    if (isType(item, 'Array')) {
      task = task.concat(item);
    }
  })
  createFlow.task = task;
  createFlow.run = function (fn) {
   if (this.task.length > 0) {
    const func = task.shift();
    const result = func();
    // 这里判断
    if (result && typeof result.then === 'function') {
      result.then(() => this.run(fn));
    } else {
      this.run(fn);
    }
   } else {
      typeof fn === 'function' && fn();
   }    
  }
  return createFlow;
}
function createFlow(arr) {
	let curList = [];
	add(arr);
	return {
		run: function (cb) {
			if(cb){
				curList.push(cb);
			}
			go(curList);
		},
		_list: curList
	}
	async function go(list) {
		for(let i = 0;i<list.length;i++){
			await list[i]();
		}
	}
	function add(list) {
		for(let i = 0;i<list.length;i++){
			let item = list[i];
			if(typeof item  === 'function'){
				curList.push(item)
			}
			if(Array.isArray(item)){
				add(item)
			}
			if(typeof item  === 'object' && item._list){
				add(item._list)
			}
		}
	}
}
commented
function createFlow(arr){
  arr.run = async (cb) => {
    for(let fn of arr.flat(99)){
      await fn()
    }
    cb && cb()
  }
  return arr
}
commented

不简洁但是把步骤拆分成了collect和run两个阶段,不进行flat

class Flow {
	constructor(task) {
		this.task = task
	}

        async run(cb) {
          await this.task()
      
          cb instanceof Function && cb()
        }
}
const runFlow = async (flow) => {
	if (flow instanceof Array) {
		for (const item of flow) {
			await runFlow(item)
		}
	} else if (flow instanceof Function) {
		await runFlow(flow())
	} else if (flow instanceof Flow) {
		await flow.run()
	} else if (flow instanceof Promise) {
		await flow
	} else {
		// static val, do nothing
		return
	}
}

const collectFlow = (flow, list = []) => {
	list.push(() => runFlow(flow))

	return list
}

export const createFlow = (flow) => {
	return new Flow(() => runFlow(collectFlow(flow, [])))
}
type Effect = (() => Promise<void> | void) | Flow;

class Flow {
  private _queue: (() => void)[];
  private _endCallback: (() => void) | undefined;

  constructor(effects: Effect[]) {
    this._queue = effects.map((effect) => {
      const fn = () => {
        Promise.resolve(effect instanceof Flow ? effect.run() : effect()).finally(() => {
          this._next();
        });
      };
      return fn;
    });
  }

  private _next() {
    const fn = this._queue.shift();
    if (fn) {
      fn();
    } else if (this._endCallback) {
      this._endCallback();
    }
  }

  public run(callback?: () => void): void {
    this._endCallback = callback;
    this._next();
  }
}

function createFlow(effects: (Effect | Effect[])[]) {
  return new Flow(effects.flat());
}

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const subFlow = createFlow([() => delay(1000).then(() => console.log("c"))]);

createFlow([
  () => console.log("a"),
  () => console.log("b"),
  subFlow,
  [() => delay(1000).then(() => console.log("d")), () => console.log("e")],
]).run(() => {
  console.log("done");
});