UNPKG

wsemi

Version:

A support package for web developer.

463 lines (435 loc) 16.5 kB
import each from 'lodash/each' import size from 'lodash/size' import values from 'lodash/values' import genPm from './genPm.mjs' import isarr from './isarr.mjs' import isobj from './isobj.mjs' import isfun from './isfun.mjs' import queue from './queue.mjs' /** * Promise的map,可設定同時處理數量 * * 若輸入rs為資料陣列則fn需將數據處理並回傳Promise,若輸入rs為Promise陣列則fn可不給,並循序執行各Promise。 * 若先行產生promise則takeLimit設定會無效,因promise已先行初始化啟動。 * 等同於Bluebird的Promise.map,而concurrency為takeLimit。 * 使用函數執行時,當takeLimit=1,等同於mapSeries,promise依序執行完畢才會調用下一個promise。 * 使用函數執行時,當takeLimit=0,等同於先行產生promise的情形,各promise直接執行視各自執行時間結束。 * * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/pmMap.test.mjs Github} * @memberOf wsemi * @param {Array} rs 輸入資料陣列,若不給fn則rs需要為Promise陣列 * @param {Function} fun 輸入循序執行值的呼叫函數 * @param {Integer} [takeLimit=0] 輸入同時處理數量整數,預設0,代表無限制 * @returns {Promise} 回傳Promise,resolve為成功結果,reject為失敗結果 * @example * * async function topAsync() { * * async function test1() { * return new Promise((resolve, reject) => { * let ms = [] * let takeLimit = 2 //通過function調用產生promise, 可受takeLimit控管同時執行數量, takeLimit=2 * pmMap([1, 2, 3, 4, 5], function (v, k) { * return new Promise(function(resolve, reject) { * let d = 400 - ((v ** 2) * 10 + 50) * console.log('call', v) * ms.push({ call: v }) * setTimeout(function() { * console.log('resolve', v, 'd', d) * ms.push({ resolve: v, d }) * resolve('#' + v) * }, d) * }) * }, takeLimit) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * //cost: (1)340, (2)310, (3)260, (4)190, (5)100 * //1core: call 2(0) -> resolve 2(310) -> call 3(310) -> resolve 3(310+260=570) * //2core: call 1(0) -> resolve 1(340) -> call 4(340) -> resolve 4(340+190=530) -> call 5(530) -> resolve 5(340+190+100=630) * console.log('test1') * let r1 = await test1() * console.log(JSON.stringify(r1)) * // test1 * // call 1 * // call 2 * // resolve 2 d 310 * // call 3 * // resolve 1 d 340 * // call 4 * // resolve 4 d 190 * // call 5 * // resolve 3 d 260 * // resolve 5 d 100 * // then ["#1","#2","#3","#4","#5"] * // [{"call":1},{"call":2},{"resolve":2,"d":310},{"call":3},{"resolve":1,"d":340},{"call":4},{"resolve":4,"d":190},{"call":5},{"resolve":3,"d":260},{"resolve":5,"d":100},{"res":["#1","#2","#3","#4","#5"]}] * * async function test2() { * return new Promise((resolve, reject) => { * let ms = [] * let takeLimit = 1 //通過function調用產生promise, takeLimit=1, 等同於mapSeries * pmMap([1, 2, 3, 4, 5], function (v, k) { * return new Promise(function(resolve, reject) { * let d = 400 - ((v ** 2) * 10 + 50) * console.log('call', v) * ms.push({ call: v }) * setTimeout(function() { * console.log('resolve', v, 'd', d) * ms.push({ resolve: v, d }) * resolve('#' + v) * }, d) * }) * }, takeLimit) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * console.log('test2') * let r2 = await test2() * console.log(JSON.stringify(r2)) * // test2 * // call 1 * // resolve 1 d 340 * // call 2 * // resolve 2 d 310 * // call 3 * // resolve 3 d 260 * // call 4 * // resolve 4 d 190 * // call 5 * // resolve 5 d 100 * // then ["#1","#2","#3","#4","#5"] * // [{"call":1},{"resolve":1,"d":340},{"call":2},{"resolve":2,"d":310},{"call":3},{"resolve":3,"d":260},{"call":4},{"resolve":4,"d":190},{"call":5},{"resolve":5,"d":100},{"res":["#1","#2","#3","#4","#5"]}] * * async function test3() { * return new Promise((resolve, reject) => { * let ms = [] * let takeLimit = 0 //通過function調用產生promise, takeLimit=0, 無限制同時執行數量 * pmMap([1, 2, 3, 4, 5], function (v, k) { * return new Promise(function(resolve, reject) { * let d = 400 - ((v ** 2) * 10 + 50) * console.log('call', v) * ms.push({ call: v }) * setTimeout(function() { * console.log('resolve', v, 'd', d) * ms.push({ resolve: v, d }) * resolve('#' + v) * }, d) * }) * }, takeLimit) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * console.log('test3') * let r3 = await test3() * console.log(JSON.stringify(r3)) * // test3 * // call 1 * // call 2 * // call 3 * // call 4 * // call 5 * // resolve 5 d 100 * // resolve 4 d 190 * // resolve 3 d 260 * // resolve 2 d 310 * // resolve 1 d 340 * // then ["#1","#2","#3","#4","#5"] * // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"resolve":3,"d":260},{"resolve":2,"d":310},{"resolve":1,"d":340},{"res":["#1","#2","#3","#4","#5"]}] * * async function test4() { * return new Promise((resolve, reject) => { * let ms = [] * let takeLimit = 2 //同takeLimit=0(無限制同時執行數量), 因rs已事先初始化就執行故無法受takeLimit控管 * let rs = [1, 2, 3, 4, 5].map(function (v, k) { * return new Promise(function(resolve, reject) { * let d = 400 - ((v ** 2) * 10 + 50) * console.log('call', v) * ms.push({ call: v }) * setTimeout(function() { * console.log('resolve', v, 'd', d) * ms.push({ resolve: v, d }) * resolve('#' + v) * }, d) * }) * }) * pmMap(rs, null, takeLimit) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * console.log('test4') * let r4 = await test4() * console.log(JSON.stringify(r4)) * // test4 * // call 1 * // call 2 * // call 3 * // call 4 * // call 5 * // resolve 5 d 100 * // resolve 4 d 190 * // resolve 3 d 260 * // resolve 2 d 310 * // resolve 1 d 340 * // then ["#1","#2","#3","#4","#5"] * // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"resolve":3,"d":260},{"resolve":2,"d":310},{"resolve":1,"d":340},{"res":["#1","#2","#3","#4","#5"]}] * * async function test5() { * return new Promise((resolve, reject) => { * let ms = [] * let takeLimit = 1 //takeLimit=1循序執行, 執行到3先catch而跳出pmMap, 故其他任務4與5不會執行 * pmMap([1, 2, 3, 4, 5], function (v, k) { * return new Promise(function(resolve, reject) { * let d = 400 - ((v ** 2) * 10 + 50) * console.log('call', v) * ms.push({ call: v }) * setTimeout(function() { * if (v === 3) { * console.log('reject', v, 'd', d) * ms.push({ reject: v, d }) * reject('#' + v) * } * else { * console.log('resolve', v, 'd', d) * ms.push({ resolve: v, d }) * resolve('#' + v) * } * }, d) * }) * }, takeLimit) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * console.log('test5') * let r5 = await test5() * console.log(JSON.stringify(r5)) * // test5 * // call 1 * // resolve 1 d 340 * // call 2 * // resolve 2 d 310 * // call 3 * // reject 3 d 260 * // catch "#3" * // [{"call":1},{"resolve":1,"d":340},{"call":2},{"resolve":2,"d":310},{"call":3},{"reject":3,"d":260},{"err":"#3"}] * * async function test6() { * return new Promise((resolve, reject) => { * let ms = [] * let takeLimit = 0 //無限制同時執行數量, 會於3先catch而跳出pmMap, 但因順發同時執行(1~5都已call), 故3 catch(260m)之後還會有resolve 1(340)與2(310) * pmMap([1, 2, 3, 4, 5], function (v, k) { * return new Promise(function(resolve, reject) { * let d = 400 - ((v ** 2) * 10 + 50) * console.log('call', v) * ms.push({ call: v }) * setTimeout(function() { * if (v === 3) { * console.log('reject', v, 'd', d) * ms.push({ reject: v, d }) * reject('#' + v) * } * else { * console.log('resolve', v, 'd', d) * ms.push({ resolve: v, d }) * resolve('#' + v) * } * }, d) * }) * }, takeLimit) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * console.log('test6') * let r6 = await test6() * console.log(JSON.stringify(r6)) * // test6 * // call 1 * // call 2 * // call 3 * // call 4 * // call 5 * // resolve 5 d 100 * // resolve 4 d 190 * // reject 3 d 260 * // catch "#3" * // [{"call":1},{"call":2},{"call":3},{"call":4},{"call":5},{"resolve":5,"d":100},{"resolve":4,"d":190},{"reject":3,"d":260},{"err":"#3"}] * // resolve 2 d 310 * // resolve 1 d 340 * * let delay = () => { * return new Promise(function(resolve, reject) { * setTimeout(function() { * resolve() * }, 700) * }) * } * await delay() * * async function test7() { * return new Promise((resolve, reject) => { * let ms = [] * pmMap({ * t1: 1, * t2: 2.345, * t3: 'abc', * }, function (v, k) { * return new Promise(function(resolve, reject) { * resolve(`k=${k},v=${v}`) * }) * },) * .then(function(res) { * console.log('then', JSON.stringify(res)) * ms.push({ res }) * resolve(ms) * }) * .catch(function(err) { * console.log('catch', JSON.stringify(err)) * ms.push({ err }) * resolve(ms) * }) * }) * } * console.log('test7') * let r7 = await test7() * console.log(JSON.stringify(r7)) * // test7 * // then ["k=t1,v=1","k=t2,v=2.345","k=t3,v=abc"] * // [{"res":["k=t1,v=1","k=t2,v=2.345","k=t3,v=abc"]}] * * } * topAsync().catch(() => {}) * */ function pmMap(rs, fun, takeLimit = 0) { let ts = {} let abort = false //pm let pm = genPm() //check if (!isarr(rs) && !isobj(rs)) { pm.reject('rs is not an array or object') return pm } //object rs to array let useObj = false if (isobj(rs)) { useObj = true let _rs = [] each(rs, (v, k) => { _rs.push({ k, v }) }) rs = _rs } //queue let q = queue(takeLimit) //message q.on('message', function(qs) { //console.log('message', JSON.stringify(qs)) //check if (abort) { return } //get let v = q.get() //console.log('get', v) //pmm let pmm if (isfun(fun)) { let uk = v.key let uv = v.value if (useObj) { uk = v.value.k uv = v.value.v } pmm = fun(uv, uk) } else { pmm = v.value } //then and catch pmm .then((res) => { //save ts[v.key] = res }) .catch((err) => { //abort abort = true //clear q.clear() //reject pm.reject(err) }) .finally(() => { //cb q.cb() //end if (size(ts) === rs.length) { //resolve pm.resolve(values(ts)) } }) }) //push, 因queue emit為即時觸發故push需放最後 each(rs, (v, k) => { q.push({ key: k, value: v, }) }) return pm } export default pmMap