九行代码实现异步事件顺序执行器
发布于 6 年前 作者 DoubleCG 3933 次浏览 来自 分享

代码

	let Sequence = function(){
		let i = -1, args = arguments, l = args.length;
		(function lambda(){
			return new Promise((next)=>{
				i ++;
				if(i<l) args[i](next);
			}).then(lambda);
		})();
	}

用法

	Sequence(
		n=>{
			// ... 
			n();
		},
		n=>{
			//...
			n();
		}
	)

实现过程:

第1步

理解promise,仅传入resolve,以减少不必要的干扰。

	let p1 = new Promise((resolve)=>{
		resolve(1);	
	});
	p1.then(console.log);
	let p2 = new Promise((resolve)=>{
		resolve(1);
	});
	p2.then( (i)=>{
		console.log(100*i)
	});

第2步:

理解promise对回调地狱的优化。

	new Promise((resolve)=>{
		let e = 1;
		resolve(e);
	}).then(
		(e)=>{
			return new Promise((resolve)=>{
				console.log(e);
				resolve(e+1);
			});
	}).then(
		(e)=>{
			return new Promise((resolve)=>{
				console.log(e);
				resolve(e+1);
			});
	}).then(console.log);

第3步

抽象代码,精简主流程。

	function promiseCount(e){
		return new Promise((resolve)=>{
			console.log(e);
			resolve(e+1);
		});
	}

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

第4步

编写执行器,把setTimeout作为测试用异步事件。执行器内部设置index作 “辅助指针” 指向当前递归事件。

	let eventArray = [
		(callback)=>{
			console.log(1);
			$.ajax({
				url,
				success(){
					callback();
				}
			})
		},
		(callback)=>{
			console.log(2);
			setTimeout(callback,100);
		},
		(callback)=>{
			console.log(3);
			setTimeout(callback,100);
		},
		(callback)=>{
			console.log(4);
			setTimeout(callback,100);
		},
		(callback)=>{
			console.log(5);
			setTimeout(callback,100);
		}
	];
	
	function Sequence(eventArray){
		let index = -1;
		(function lambda(){
			return new Promise((resolve)=>{
				index ++;	//	指向队列中的下一个事件
				if(index<eventArray.length){
					eventArray[index](resolve);	//	把resolve交由当前事件的回调函数处理,即当前事件执行完之后就会执行then中新的lambda,得到的效果是“同步”
				}
			}).then(lambda);
		})();
	}
	
	Sequence(eventArray);

第5步 (选看)

增加数据收集器

	let eventArray = [
		(callback)=>{
			console.log(1);
			setTimeout(function(){
				callback().collector.z=0;
				callback().resolve();
			},100);
		},
		(callback)=>{
			console.log(2);
			setTimeout(function(){
				callback().collector.a=1;
				callback().resolve();
			},100);
		},
		(callback)=>{
			console.log(3);
			setTimeout(function(){
				callback().collector.b=2;
				callback().resolve();
			},100);
		},
		(callback)=>{
			console.log(4);
			setTimeout(function(){
				callback().collector.c=3;
				callback().resolve();
			},100);
		},
		(callback)=>{
			console.log(5);
			setTimeout(function(){
				callback().collector.d=4;
				callback().resolve();
			},100);
		}
	];

	let Sequence = function(eventArray){
		let index = -1, events = eventArray;
		let collector = {};
		(function lambda(){
			return new Promise((resolve)=>{
				index ++;
				if(index<events.length){
					events[index](()=>{
						return {
							resolve,
							collector,
						}
					});
				}
			}).then(lambda);
		})();
	
		this.getData = function(){
			return collector;
		}
		this.clear = function(){
			collector = null;
		}
	}
	let ev = new Sequence(eventArray);
	setTimeout(function(){
		console.log(ev.getData())
	},2000);

collector会一直存在ev对象的内部,仅能通过调用getData获取。当然还可以往Sequence里添加更多方法。

第6步

提炼核心, 并把 resolve 改为 next.

	let Sequence = function(eventArray){
	  let index = -1;
	  (function lambda(){
		  return new Promise((next)=>{
			  index ++;
			  if(index<eventArray.length){
				  eventArray[index](next);
			  }
		  }).then(lambda);
	  })();
  	}
new Sequence([
	(next)=>{
		setTimeout(()=>{
			// do something
			console.log(1);
			next();
		},100);
	},
	(next)=>{
		setTimeout(()=>{
			// do something
			console.log(2);
			next();
		},100);
	},
	(next)=>{
		setTimeout(()=>{
			// do something
			console.log(3);
			next();
		},100);
	},
	(next)=>{
		setTimeout(()=>{
			// do something
			console.log(4);
			next();
		},100);
	},
	(next)=>{
		setTimeout(()=>{
			// do something
			console.log(5);
			next();
		},100);
	}
]);

最后一步

再次精简代码并提炼写法,减少调用键入次数。

let Sequence = function(){
	let i = -1, args = arguments, l = args.length;
	(function lambda(){
		return new Promise((next)=>{
			i ++;
			if(i<l) args[i](next);
		}).then(lambda);
	})();
}

new Sequence(
	next=>{
		setTimeout(()=>{
			// do something
			console.log(1);
			next();
		},100);
	},
	next=>{
		setTimeout(()=>{
			// do something
			console.log(2);
			next();
		},100);
	},
	next=>{
		setTimeout(()=>{
			// do something
			console.log(3);
			next();
		},100);
	},
	next=>{
		setTimeout(()=>{
			// do something
			console.log(4);
			next();
		},100);
	},
	next=>{
		setTimeout(()=>{
			// do something
			console.log(5);
			next();
		},100);
	}
);

最终结果

  let Sequence = function(){
	  let i = -1, args = arguments, l = args.length;
	  (function lambda(){
		  return new Promise((next)=>{
			  i ++;
			  if(i<l) args[i](next);
		  }).then(lambda);
	  })();
  }

发布于 NPM, 安装:npm i zfc-sequence

最终用法

let sequence = require('zfc-sequence');
  sequence(
	  next=>{
		 setTimeout(()=>{
			// ...
			next();
		 },1000)		 
	  },
	  next=>{
		 setTimeout(()=>{
			// ...
			next();
		 },1000)	
	  },
	  next=>{
		 setTimeout(()=>{
			// ...
			next();
		 },1000)	
	  },
	  next=>{
		 setTimeout(()=>{
			// ...
			next();
		 },1000)	
	  }
  );
13 回复

点个赞👍实现得很精髓.

if(i<l) args[i](next);

// 改成 l < args.length
// 是不是就能动态添加任务,只要队列没有结束
if(i<args.length) args[i](next);

@axetroy 如果可以再补充。

怎么for循环添加任务

实现的不错,那这个和.then里面返回promise,再点then,功能上有什么区别呢? 比如

new Promise((reslove,rej)=>{
    setTimeout(()=>{
        reslove(1);
    },1000)
}).then((res)=>{
    console.log(res)
    return new Promise((reslove,rej)=>{
        setTimeout(()=>{
            reslove(2);
        },1000)
    });
}).then((res)=>{
    console.log(res)
})

666 在嵌入式领域,这个叫“状态机”,很棒的程序思路

@zy445566 理解第一步和第三步,并要明白 next 拿到外部调用,实际上指向相同内存。

已有的实现比较好的库可以参考 https://github.com/caolan/async

@axetroy 动态添加事件补充, 不可避免地写成三角形。 总会找到更好的写法, async很好了, 感觉是扩展了底层? 而动态添加事件的应用场景在哪里?

let Sequence = function(){
	let i = -1, args = arguments;

	(function lambda(){
		return new Promise((next)=>{
			i ++;
			if(i<args.length) args[i]({
				next,
				push(){
					Array.prototype.push.call(args,...arguments);
				}
			});
		}).then(lambda);
	})();
}


let r = 0;
Sequence(
	o=>{
		console.time('test');
		setTimeout(()=>{
			console.log(r++);
			o.push(
				o=>{
					setTimeout(()=>{
						console.log('动态添加的第一事件');
						o.next();
					})
				},
				o=>{
					setTimeout(()=>{
						console.log('动态添加的第二事件');
						o.next();
					})
				}
			)
			o.next();
		})
	},
	o=>{
		setTimeout(()=>{
			console.log(r++);
			o.push(o=>{
				setTimeout(()=>{
					console.log('动态添加的第三事件');
					o.next();
				})
			})
			o.next();
		})
	},
	o=>{
		setTimeout(()=>{
			console.log(r++);
			o.push(o=>{
				setTimeout(()=>{
					console.log('动态添加的第四事件');
					o.next();
				})
			})
			o.next();
		})
	}
)

@zhhb async 不是对 generator 的封装吗?

@DoubleCG 以前我写怕从的时候,也封装过类似的库。

大概是这样: 你不知道爬取的数据有多少条 (任务数量未知), 只能发现一条就 push 一条 (动态添加)

然后还保持任务池内的任务,最多并发 x 个任务…

@axetroy 可以自己写一个队列,有符合条件的事件就push进去,然后用setInterval控制并发每次只处理队头前n个事件? 但这仍然不是真正意义上的单线并发控制。。。仅仅控制了处理n个事件的间隔,不能控制时间段内的并发事件数。不知道我有没表达明白。

来自酷炫的 CNodeMD

回到顶部