Skip to content

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

Open

Description

@sisterAn
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 的执行只需要支持串行。

Activity

  1. sisterAn commented on Sep 9, 2020

    @sisterAn
    OwnerAuthor

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

  2. m7yue commented on Sep 10, 2020

    @m7yue
      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'))
  3. m7yue commented on Sep 10, 2020

    @m7yue

    之前会错意了,思路一样,利用 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 }
      }
  4. kexiaofu commented on Sep 11, 2020

    @kexiaofu

    其实我还有一个思路:

    • 收集参数部分相同;

    • 在 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;
    }
  5. xiaowuge007 commented on May 12, 2021

    @xiaowuge007
    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)
    			}
    		}
    	}
    }
  6. NoBey commented on Mar 23, 2022

    @NoBey
    function createFlow(arr){
      arr.run = async (cb) => {
        for(let fn of arr.flat(99)){
          await fn()
        }
        cb && cb()
      }
      return arr
    }
  7. Athaoo commented on Aug 8, 2023

    @Athaoo

    不简洁但是把步骤拆分成了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, [])))
    }
  8. front-end-captain commented on Jun 10, 2024

    @front-end-captain
    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions