Daily-Interview-Question icon indicating copy to clipboard operation
Daily-Interview-Question copied to clipboard

第182题:实现一个异步求和函数

Open sisterAn opened this issue 3 years ago • 10 comments

提供一个异步 add 方法如下,需要实现一个 await sum(...args) 函数:

function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 1000);
}

sisterAn avatar Apr 28 '21 06:04 sisterAn

function asyncAdd(a, b, callback) {
    setTimeout(() => {
        callback(null, a + b)
    }, 1000)
}

// 封装一个promise版的add函数
function add(a, b) {
    return new Promise(resolve => {
        asyncAdd(a, b, (_, sum) => {
            resolve(sum)
        })
    })
}

function sum(...args) {
    return new Promise(resolve => {
        args.reduce((p, n) => p.then(total => add(total, n)), Promise.resolve(0)).then(resolve)
    })
}

;(async () => {
    const result = await sum(1, 2, 3, 4)
    console.log(result)
})()

champkeh avatar Apr 29 '21 05:04 champkeh

简化:两数之和

我们先来简单的实现一个异步两数之和函数

function sumT(a, b) {
    return await new Promise((resolve, reject) => {
        asyncAdd(a, b, (err, res) => {
            if(!err) {
                resolve(res)
            }
            reject(err)
        })
    })
}

// 测试
const test = await sumT(1, 2)
console.log(test)
// 3

加深:多数之和

上面我们实现了两数之和,然后扩展到多数之和喃?

提到数组求和问题,我们首先想到的是 reduce

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

—— MDN

arr.reduce(callback(acc, cur[, idx[, arr]])[, initialValue])

callback 函数接收4个参数:

  • acc :累计器
  • cur :当前值
  • idx : 当前索引
  • arr :源数组

其中, initialValue 可选,

  • 如果有 initialValueacc 取值为 initialValuecur 取数组中的第一个值
  • 如果没有:acc 取数组中的第一个值, cur 取数组中的第二个值
const arr = [1, 2, 3, 4];
const reducer = (acc, cur) => acc + cur;

// 1 + 2 + 3 + 4
console.log(arr.reduce(reducer));
// 输出: 10

// 5 + 1 + 2 + 3 + 4
console.log(arr.reduce(reducer, 5));
// 输出: 15

关于本题:来自@champkeh

设置初始值为 Promise.resolve(0) ,经历 5 次求和:

function sum(...args) {
    return new Promise(resolve => {
        args.reduce((acc, cur) => acc.then(total => sumT(total, cur)), Promise.resolve(0)).then(resolve)
    })
}

// 测试
await sum(1, 2, 3, 4, 5)
// 15

但这存在一个耗时较长的问题,我们可以计算下时间:

console.time("sum")
// 测试
await sum(1, 2, 3, 4, 5)
// 15
console.timeEnd("sum")

也就是说,我们每次求和都会花费 1s,串行异步求和,这显然不是最优的

优化:使用 Promise.all

我们可以两两一组,使用 Promise.all 求和,再把和两两一组继续求和…..,知道只剩余一个就是最终的结果

async function sum(...args) {
    // 用于考察每次迭代的过程
    console.log(args) 
    
    // 如果仅有一个,直接返回
    if(args.length === 1) return args[0]
    let result = []
    // 两两一组,如果有剩余一个,直接进入
    for(let i = 0; i < args.length - 1; i+=2) {
        result.push(sumT(args[i], args[i + 1]))
    }
    if(args.length%2)  result.push(args[args.length-1])
    // Promise.all 组内求和
    return sum(...await Promise.all(result))
}

// 测试
test = await sum(1, 2, 3, 4, 5)
// 15

console.time("sum")
await sum(1, 2, 3, 4, 5)
console.timeEnd("sum")

sisterAn avatar Apr 29 '21 15:04 sisterAn

function asyncAdd(a, b) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      res(a + b);
    }, Math.random() * 1000);
  });
}
function sum() {
  let args = [...arguments];
  let count = 0;
  function checkArray(arr, res) {
    if (arr.length >= 2) {
      count += 1;
      asyncAdd(...arr.splice(0, 2)).then(result => {
        count -= 1;
        arr.push(result);
        checkArray(arr, res);
      });
      checkArray(arr, res);
    } else {
      if (count === 0) {
        res(arr[0]);
      }
    }
  }
  return new Promise((res, rej) => {
    if (args.length === 1) {
      res(args[0]);
    } else {
      checkArray(args, res);
    }
  });
}
sum(1, 2, 3, 4, 5).then(result => {
  console.log(result);
});

大佬们,这个是不是会快一点,Promise.all如果两组同时进行的话,其中一组快,一组慢的话就会出现快的那组等待慢组执行完才能去跟后面的相加,但是这块时间本来可以利用起来跟后面的先进行计算的,Promise.all有点浪费多余时间了吧,求大佬指点!

w979897702 avatar May 11 '21 10:05 w979897702

  function asyncAdd() {
      return new Promise((resolve, reject) => {
        try {
          resolve([].slice.call(arguments).reduce((sum, item, index, originArr) => {
            if (typeof item !== 'number') throw new Error('参数不是纯数字')
            return sum + item
          }, 0))
        } catch (error) {
          reject(error)
        }
      })
    }
    asyncAdd(1, 2, 3, 10).then(res => console.log(res)) // 3

不晓得是不是这个意思哦 ,可以传入参数列表

shengdan19 avatar Jun 04 '21 06:06 shengdan19

const log = console.log.bind(console);
function add(a, b, cb) {
  setTimeout(() => {
    cb(a + b);
  }, 100);
}

let sumTwo = (a, b) => {
  return new Promise((resolve) => {
    add(a, b, resolve);
  });
};

function sum(...args) {
  return new Promise((resolve) => {
    let total = args.reduce((prev, cur) => {
      if (prev instanceof Promise) {
        return prev.then((res) => sumTwo(res, cur));
      }
      return sumTwo(prev, cur);
    });
    resolve(total);
  });
}

async function sum2(...args) {
  log(args);
  if (args.length === 1) {
    return args[0];
  }

  let promiseList = [];

  for (let i = 0; i < args.length - 1; i += 2) {
    promiseList.push(sumTwo(args[i], args[i + 1]));
  }

  if (args.length % 2 !== 0) {
    promiseList.push(args[args.length - 1]);
  }

  let resList = await Promise.all(promiseList);
  return sum2(...resList);
}

async function test() {
  console.time("耗时");
  let arr = new Array(10).fill(0).map((x, index) => {
    return 10 ** index;
  });
  let res = await sum2(...arr);
  console.log("final", res);
  console.timeEnd("耗时");
}

async function test2() {
  // console.log(this.name);
  console.time("耗时2");
  let arr = new Array(10).fill(0).map((x, index) => {
    return 10 ** index;
  });
  let res = await sum(...arr);
  console.log("final", res);
  console.timeEnd("耗时2");
}

test();
test2();

holynova avatar Sep 12 '21 09:09 holynova


// 实现一个异步求和函数
// 提供一个异步 add 方法如下,需要实现一个 await sum(...args) 函数:
function asyncAdd(a, b, callback) {
    setTimeout(function () {
        callback(null, a + b);
    }, 1000);
}

function asyncAddProm(a, b) {
  return new Promise((rs, rj) => {
    asyncAdd(a, b, (err, res) => {
      if (err) {
        rj(err);
      } else {
        rs(res);
      }
    });
  });
}


function sum(...args) {
    return args.reduce((acc, cur) => acc.then(total => asyncSum(cur, total)), Promise.resolve(0));
}

async function main() {
    console.time("calc")
    const res = await sum(1, 2, 3, 4, 5);
    console.log(res);
    console.timeEnd("calc")
}

main();

Yangfan2016 avatar Aug 18 '22 13:08 Yangfan2016

使用 Promise.all:

function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 1000);
}

function promiseAdd(a, b) {
  return new Promise((resolve) => {
    asyncAdd(a, b, (_, sum) => resolve(sum));
  });
}

async function sum(...args) {
  const promises = [];

  for (let i = 0; i < args.length; i += 2) {
    if (typeof args[i + 1] !== 'undefined') {
      promises.push(promiseAdd(args[i], args[i + 1]));
    } else {
      promises.push(Promise.resolve(args[i]));
    }
  }

  const result = await Promise.all(promises);

  if (result.length === 1) return result[0];

  return sum(...result);
}

(async () => {
  console.time('time');
  console.log(await sum(1, 2, 3, 4, 5));
  console.timeEnd('time');
})();

Dylan0916 avatar Aug 21 '23 04:08 Dylan0916

// 提供一个异步 add 方法如下,需要实现一个 await sum(...args) 函数:
function asyncAdd(a, b, callback) {
  setTimeout(function () {
    callback(null, a + b);
  }, 1000);
}

function add(a, b) {
  return new Promise((resolve) => {
    asyncAdd(a, b, (_, sum) => {
      resolve(sum);
    });
  });
}

// 方法1
function sum1(...args) {
  return new Promise((resolve) => {
    resolve(
      args.reduce(async (p, c) => await add(await Promise.resolve(p), c), 0)
    );
  });
}

// 方法2
function sum2(...args) {
  return new Promise((resolve) => {
    args
      .reduce((p, c) => Promise.resolve(p).then((total) => add(total, c)), 0)
      .then(resolve);
  });
}

// 使用
(async function () {
  const result1 = await sum1(1, 2, 3, 4);
  const result2 = await sum12(1, 2, 3, 4);
  console.log(result1, result2);
})();


nqsjd178559344 avatar Sep 12 '23 02:09 nqsjd178559344

分享一个更简洁的写法

function asyncAdd(a, b, callback) {
    setTimeout(function() {
        callback(null, a + b)
    }, 1000)
}


function sum() {
    const queue = Array.from(arguments);
    let sum = queue.shift();

    return new Promise((resolve, reject) => {
        function next() {
            if (queue.length > 0) {
                asyncAdd(sum, queue.shift(), (err, res) => {
                    console.log("%c Line:16 🌽 res", "color:#fca650", res);
                    sum = res;
                    next();
                })
            } else {
                resolve(sum);
            }
        }
        next();
    });
}

sum(1,2,3,4,5,6,7,8,9,10,11).then(sum => console.log(sum))  

// Line:16 🌽 res 3
// Line:16 🌽 res 6
// Line:16 🌽 res 10
// Line:16 🌽 res 15
// Line:16 🌽 res 21
// Line:16 🌽 res 28
// Line:16 🌽 res 36
// Line:16 🌽 res 45
// Line:16 🌽 res 55
// Line:16 🌽 res 66
// 66

zhaocchen avatar Mar 08 '24 10:03 zhaocchen

我的实现思路就是二分法,不停地二分,直到满足终止条件。

function asyncAdd(a = 0, b = 0, callback) {
    setTimeout(function() {
        callback(a + b)
    }, 1000)
}

const add = (a = 0, b = 0) => new Promise((resolve) => asyncAdd(a, b, resolve));

function sum(...args) {
    if (args.length === 1) return new Promise((resolve) => resolve(args));
    if (args.length === 2) {
        return new Promise((resolve) => asyncAdd(args[0], args[1], resolve));
    }

    let params = [];
    for (let i = 0; i < args.length; i += 2) {
        if (i + 1 < args.length) {
            params.push([args[i], args[i + 1]]);
        } else {
            params.push([args[i]]);
        }
    }

    let fns = []

    params.forEach(([a, b]) => {
        fns.push(add(a, b));
    });

    return Promise.allSettled(fns).then((res) => {
        const answers = res.map(item => item.value);
        console.log(answers)
        return sum(...answers);
    })
}

Inchill avatar Apr 15 '24 02:04 Inchill