在js里面,偶爾會遇見需要多個異步按照順序執行請求,又不想多層嵌套,,這里和promise.all的區別在於,promise或者Jquery里面的$.when 是同時發送多個請求,一起返回,發出去的順序是一起;這里是按照順序發請求
方法 一 、首先創建一個迭代器,接收任意多個函數參數
function nextRegister(){ var args = arguments; var count = 0; var comm = {}; function nextTime(){ count++; if(count < args.length){ if(args[count] && Object.prototype.toString.call(args[count]) == '[object Function]'){ args[count](comm,nextTime); } } } if(args[count] && Object.prototype.toString.call(args[count]) == '[object Function]'){ args[count](comm,nextTime); } }
創建多個異步的函數,注入到迭代器中
/* comm:多個函數,公用的變量 next:調用下一個函數 * */ function fn1(comm,next){ console.log('1'); comm.age = 20; next(); } function fn2(comm,next){ next(); console.log('2'); console.log(comm.age); } function fn3(comm,next){ console.log('3'); } //開始執行迭代 nextRegister(fn1,fn2,fn3);
在這里,fn1-fn3函數中,做異步操作,知道在異步成功的時候調用next()就可以繼續執行下一個函數,同時可以將前面函數返回的結果,綁定在comm上,帶到下一個函數中
方法 二、參考express 和 koa 的寫法
1、es5寫法
function Iterator(){ this.middlewares = []; } Iterator.prototype.use = function(fn){ this.middlewares.push(fn); return this; } Iterator.prototype.run = function(ctx){ function createNext(middleware, oldNext) { return function(){ middleware(ctx, oldNext) }; } let len = this.middlewares.length; let next = function(){}; for (let i = len - 1; i >= 0; i--) { let currentMiddleware = this.middlewares[i]; next = createNext(currentMiddleware, next); //從后往前遍歷,前面的閉包保存后面的 next } next(); } var iterator = new Iterator(); iterator.use(function(ctx,next){ //這里可以做一些異步操作,只需要成功后調用 next console.log("start:a"); next(); console.log("end:a"); }); iterator.use(function(ctx,next){ console.log("start:b"); next(); console.log("end:b"); }); iterator.run();
2、es6 的 async 寫法
class Iterator{ constructor(){ this.middlewares = []; } use(fn){ this.middlewares.push(fn); //存入任務 return this; } async run(ctx){ function createNext(middleware, oldNext) { return async () => { await middleware(ctx, oldNext); } } let len = this.middlewares.length; let next = async () => { return Promise.resolve(); }; for (let i = len - 1; i >= 0; i--) { let currentMiddleware = this.middlewares[i]; next = createNext(currentMiddleware, next); } await next(); } } let app = new Iterator(); app.use(async (ctx,next)=>{ console.log("start:a"); await next(); console.log("end:a"); }); app.use(async (ctx,next)=>{ console.log("start:b"); await next(); console.log("end:b"); }); app.run();
三 、擴展 基於上面的 二 的迭代器,利用 node 的 原生 http 模塊,可以簡單的創建一個服務框架。
這里寫一個簡單的靜態文件查看服務
const http = require('http'); const fs = require("fs"); const promisify = require("util").promisify; const readFile = promisify(fs.readFile); const readdir = promisify(fs.readdir); const path = require("path"); //創建web服務器,提供服務,處理客戶端的請求 //普通方式監聽 class MyServer{ constructor() { const me = this; me.middlewares = []; me.methods = {}; me.server = http.createServer(async (req,res)=>{ //req客戶端請求的相關信息,res返回響應信息 //let url = req.url; let ctx = { res, req, stateCode:200, headers:{'Content-Type': 'text/plain;charset=utf-8'}, send(str,headers){ let res = ctx.res; res.writeHead(ctx.stateCode || 200, headers || ctx.headers); res.end(str); } }; await me.run(ctx); //開始執行任務 }); me.server.on("error",function(e){ me.trigger("error",e); }); me.server.on("listening",function(e){ var addr = me.server.address(); var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; console.log('Listening on ' + bind); }) } listen(port){ this.server.listen(port || 9000); return this; } use(fn){ this.middlewares.push(fn); //存入任務 return this; } async run(ctx){ function createNext(middleware, oldNext) { return async () => { await middleware(ctx, oldNext); } } let len = this.middlewares.length; let next = async () => { return Promise.resolve(); }; for (let i = len - 1; i >= 0; i--) { let currentMiddleware = this.middlewares[i]; next = createNext(currentMiddleware, next); } try{ await next(); }catch(e){ //解決中文亂碼 this.trigger("error",e); } } on(type,fn){ if(!this.methods[type]){ this.methods[type] = []; } this.methods[type].push(fn); } trigger(type,arg){ if(this.methods[type]){ for(var i = 0; i < this.methods[type].length; i++){ this.methods[type][i](arg); } }else if(type === "error"){ throw new Error(arg); } } } var server = new MyServer(); //錯誤處理 server.use(async (ctx,next)=>{ try{ await next(); }catch(e){ ctx.stateCode = 500; ctx.send('服務器錯誤:' + e.message); } }); //開啟跨域 server.use(async ({res},next)=>{ res.setHeader("Access-Control-Allow-Origin", "*"); // 設置可訪問的源 res.setHeader("Access-Control-Allow-Headers", "Content-Type"); next(); }); //其他邏輯正常處理 const base = __dirname; //默認是當前文件為靜態資源目錄, 可以為 "F:\\work" //嘗試讀取文件 server.use(async (ctx,next)=>{ //初始化路徑 if(ctx.req.url === "/"){ ctx.filePath = base || __dirname; }else{ ctx.filePath = path.join(base || __dirname,decodeURIComponent(ctx.req.url)); } try{ let res = await readFile(ctx.filePath); ctx.send(res); }catch(e){ next(); } }); //嘗試讀取 index.html server.use(async (ctx,next)=>{ try{ if(ctx.filePath.indexOf(".") === -1){ let res = await readFile(ctx.filePath+"/index.html","utf-8"); ctx.headers["Content-Type"] = "text/html;charset=utf-8"; ctx.send(res); }else{ next(); } }catch(e){ next(); } }); //嘗試讀取文件夾, 並展示 server.use(async (ctx,next)=>{ try{ let res = await readdir(ctx.filePath); var str = []; var prex = ctx.req.url; if(ctx.req.url !== "/"){ var parent = path.dirname(ctx.req.url); str = [ `<h3><a href="${parent}">.../返回上級</a> 共 ${res.length} 個文件</h3>` ]; }else{ prex=""; } for(let i = 0; i < res.length; i++){ str.push(`<p><a href="${prex}/${res[i]}">${res[i]}</a></p>`) } var html = `<div style="text-align:center;">${str.join("")}</div>`; ctx.headers["Content-Type"] = "text/html;charset=utf-8"; ctx.send(html); }catch(e){ console.log(e) next(); } }); //404處理 server.use(async (ctx,next)=>{ ctx.stateCode = 404; ctx.send('not found:'+ctx.req.url); }); server.listen(3001);