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

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

Open
sisterAn opened this issue Sep 9, 2020 · 8 comments
Labels

Comments

@sisterAn
Copy link
Owner

sisterAn commented Sep 9, 2020

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 的执行只需要支持串行。
@sisterAn sisterAn added the 阿里 label Sep 9, 2020
@sisterAn
Copy link
Owner Author

sisterAn commented Sep 9, 2020

在本题中, 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

@m7yue
Copy link

m7yue commented Sep 10, 2020

  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'))

@m7yue
Copy link

m7yue commented Sep 10, 2020

之前会错意了,思路一样,利用 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 }
  }

@kexiaofu
Copy link

其实我还有一个思路:

  • 收集参数部分相同;

  • 在 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;
}

@xiaowuge007
Copy link

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)
			}
		}
	}
}

@NoBey
Copy link

NoBey commented Mar 23, 2022

function createFlow(arr){
  arr.run = async (cb) => {
    for(let fn of arr.flat(99)){
      await fn()
    }
    cb && cb()
  }
  return arr
}

@Athaoo
Copy link

Athaoo commented Aug 8, 2023

不简洁但是把步骤拆分成了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, [])))
}

@front-end-captain
Copy link

front-end-captain commented Jun 10, 2024

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");
});

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

No branches or pull requests

7 participants