Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 160 题:输出以下代码运行结果,为什么?如果希望每隔 1s 输出一个结果,应该如何改造?注意不可改动 square 方法 #389

Open
yygmind opened this issue May 27, 2020 · 65 comments

Comments

@yygmind
Copy link
Contributor

yygmind commented May 27, 2020

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

function test() {
  list.forEach(async x=> {
    const res = await square(x)
    console.log(res)
  })
}
test()
@sl1673495
Copy link

sl1673495 commented May 27, 2020

forEach是不能阻塞的,默认是请求并行发起,所以是同时输出1、4、9。

串行解决方案:

async function test() {
  for (let i = 0; i < list.length; i++) {
    let x = list[i]
    const res = await square(x)
    console.log(res)
  }
}

当然,也可以用 for of 语法,就是帅:

async function test() {
  for (let x of list) {
    const res = await square(x)
    console.log(res)
  }
}

还有一个更硬核点的,也是 axios 源码里所用到的,利用 promise 本身的链式调用来实现串行。

let promise = Promise.resolve()
function test(i = 0) {
  if (i === list.length) return
  promise = promise.then(() => square(list[i]))
  test(i + 1)
}
test()

@linsicong003
Copy link

一秒后同时输出 1、4、9

如果要每隔一秒输出把 forEach 换成普通 for 循环或者 for...of... 循环即可

这里并行进行是因为 forEach 实现的问题,源码里用 while 来一次性执行了所有回调

具体参考官网 polyfill: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

@edison1105
Copy link

forEach是不能阻塞的,默认是请求并行发起,所以是同时输出1、2、3。

串行解决方案:

async function test() {
  for (let i = 0; i < list.length; i++) {
    let x = list[i]
    const res = await square(x)
    console.log(res)
  }
}

同时输出的是 1、2、3 的平方也就是 1 、4、9

@bighamD
Copy link

bighamD commented May 27, 2020

async function test() {
  var n = list.length;

    while(n--) {
        const res = await s(list[n]);
        console.log(res);
    }
}
每隔一秒输出 9 4 1

@bighamD
Copy link

bighamD commented May 27, 2020

一秒后同时输出 1、4、9

如果要每隔一秒输出把 forEach 换成普通 for 循环或者 for...of... 循环即可

这里并行进行是因为 forEach 实现的问题,源码里用 while 来一次性执行了所有回调

具体参考官网 polyfill: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

你这个用 while 来一次性执行了所有回调描述不太准确吧,普通的for 等于同一个块作用域连续await,而forEach的回调是一个个单独的函数,跟其他回调同时执行,互不干扰

function test() {
    list.forEach(async x=> {
      const res = await square(x)
      console.log(res)
    })

    //forEach循环等于三个匿名函数;
    (async (x) => {
        const res = await square(x)
        console.log(res)
    })(1);
    (async (x) => {
        const res = await square(x)
        console.log(res)
    })(2);
    (async (x) => {
        const res = await square(x)
        console.log(res)
    })(3);

    // 上面的任务是同时进行
  }

  async function test() {
    for (let x of list) {
      const res = await square(x)
      console.log(res)
    }
  }
  //等价于

  async function test() {
      const res = await square(1)
      console.log(res)
      const res2 = await square(2)
      console.log(res)
      const res3 = await square(3)
      console.log(res)
  }

@Dayong99
Copy link

@sl1673495 窃以为您的第三个串行方案的第四行代码后面应该加上.then(res => console.log(res))

@ghost
Copy link

ghost commented May 27, 2020

《ES6 入门》中还提到了使用 reduce 解决:

list.reduce(async (_, x) => {
  await _
  const res = await square(x)
  console.log(res)
}, undefined)

@fariellany
Copy link

fariellany commented May 27, 2020

直接在forEach 里面套个setTimeOut 不就可以了

function test() {
list.forEach(async x => {
setTimeout(async () => {
const res = await square(x)
console.log(res)
}, 1000 * x)
})
}

@fariellany
Copy link

《ES6 入门》中还提到了使用 reduce 解决:

list.reduce(async (_, x) => {
  await _
  const res = await square(x)
  console.log(res)
}, undefined)

await _ 这个怎么理解呀 大哥

@ghost
Copy link

ghost commented May 27, 2020

《ES6 入门》里面是这样介绍的:
reduce 方法的第一个参数是 async 函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用await等待它操作结束

如果没有 await _ 这一行,得到的结果和 forEach() 是一样的,可以理解为 @linsicong003 所说的:源码里用 while 来一次性执行了所有回调

添加一行打印:

function test() {
  list.reduce(async (_, x) => {
    console.log(_, x) // 打印
    await _
    const res = await square(x)
    console.log(res)
  }, undefined)
}

test()

打印结果:

马上打印:
undefined 1
Promise { <pending> } 2
Promise { <pending> } 3

每隔一秒打印:
1
4
9

@hanxu317317
Copy link

为什么大厂面试的题都这么蹊跷???

@gitzhaochen
Copy link

gitzhaochen commented May 28, 2020

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}
function test() {
  ;(async () => {
    for await (let num of list) {
      const res = await square(num)
      console.log(res)
    }
  })()
}
test()

@zengfan0605
Copy link

zengfan0605 commented May 29, 2020

const list = [1, 2, 3]
const square = num => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

function test() {
    list.forEach(x=> {
        setTimeout(async x => {
            const res = await square(x)
            console.log(res)
        }, x*1000, x)
    })
}
test()

@hyden-tan
Copy link

hyden-tan commented May 29, 2020

function test() {
 list.reduce(
   (pre, cur) => pre.then(() => square(cur)).then(console.log),
   Promise.resolve()
 );
}

test();

@qianlongo
Copy link

// 解法一,for循环

async function test() {
  for (let i = 0, len = list.length; i < len; i++) {
    const res = await square(list[i])
    console.log(res)
  }
}
// 解法2 for of循环
async function test() {
  for (x of list) {
    const res = await square(x)
    console.log(res)
  }
}
// 解法3 类似koa里面的compose思想解决
async function test() {
  const compose = (middleware, next) => {
    let index = -1
    return (ctx) => {
      const dispatch = (i) => {
        if (index < i ) {
          index = i
        }
        let fn = middleware[ index ]

        if (index === middleware.length) {
          fn = next
        }

        if (!fn) {
          return Promise.resolve()
        }

        return Promise.resolve(fn(ctx, async () => {
          return dispatch(index + 1)
        }))
      }

      dispatch(0)
    }
  }
  
  const middleware = []
  list.forEach((it) => {
    middleware.push(async (ctx, next) => {
      const res = await square(it)
      console.log(res)
      await next()
    })
  })

  compose(middleware, () => {
    console.log('end')
  })({ name: 'qianlongo' })
}

// 解法4 next思想 也是koa1的中间件执行思想

async function test() {
  let middleware = []

  list.forEach((it) =>{
    middleware.push(async (cb) => {
      const res = await square(it)
      console.log(res)
      cb && cb()
    })
  })
  
  const bindNext = (cbs) => {
    let next = function () {
      console.log('111')
    }
    let len = cbs.length

    while (len--) {
      next = cbs[ len ].bind(null, next)
    }

    return next
  }

  bindNext(middleware)()
}

// 解法5 利用promise的链式调用
function test() {
  let promise = Promise.resolve()

  list.forEach(x => {
    promise = promise.then(() => square(x)).then((res) => {
      console.log(res)
    })
  })
}


test()

@YuGao
Copy link

YuGao commented Jun 6, 2020

题目本身有些问题,定时功能实现 square 中需要进行误差修正的

@fariellany
Copy link

forEach的回调是一个个单独的函数,跟其他回调同时执行

精品

@fariellany
Copy link

《ES6 入门》中还提到了使用 reduce 解决:

list.reduce(async (_, x) => {
  await _
  const res = await square(x)
  console.log(res)
}, undefined)

大哥 这里面都没有return 为什么await _ (_) 会是上一次的方法呢 怎么累积的呢 ???
传统的 不都是这样的吗
var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
能帮忙解答下吗 迷茫。。。。。

@xujie-phper
Copy link

forEach是不能阻塞的,默认是请求并行发起,所以是同时输出1、4、9。

串行解决方案:

async function test() {
  for (let i = 0; i < list.length; i++) {
    let x = list[i]
    const res = await square(x)
    console.log(res)
  }
}

当然,也可以用 for of 语法,就是帅:

async function test() {
  for (let x of list) {
    const res = await square(x)
    console.log(res)
  }
}

还有一个更硬核点的,也是 axios 源码里所用到的,利用 promise 本身的链式调用来实现串行。

let promise = Promise.resolve()
function test(i = 0) {
  if (i === list.length) return
  promise = promise.then(() => square(list[i]))
  test(i + 1)
}
test()

promise的实现根本没有输出呢

@liuweitao111
Copy link

《ES6 入门》中还提到了使用 reduce 解决:

list.reduce(async (_, x) => {
  await _
  const res = await square(x)
  console.log(res)
}, undefined)

大哥 这里面都没有return 为什么await _ (_) 会是上一次的方法呢 怎么累积的呢 ???
传统的 不都是这样的吗
var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
能帮忙解答下吗 迷茫。。。。。

因为async函数的返回值是 Promise 对象

const asyncFunc = async () => {}
console.log(asyncFunc()) // Promise {<resolved>: undefined}

@lanshanmao
Copy link

一道题可以导出好多知识点,受教了各位大佬

@darkestinblack
Copy link

darkestinblack commented Jul 14, 2020

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}
//迭代器实现
async function test () {
  var iter = list[Symbol.iterator]();
  var flag = iter.next();
  while (!flag.done) {
    await square(flag.value).then(res => console.log(res));
    flag = iter.next();
  }
}
test();

@DerrickTel
Copy link

forEach大概可以这么理解

Array.prototype.forEach = function (callback) {
  // this represents our array
  for (let index = 0; index < this.length; index++) {
    // We call the callback for each entry
    callback(this[index], index, this)
  }
}

所以是1秒后输出1, 4, 9; 几乎同时;但是有调用先后顺序;
改进的话, 大家都答了, 我就不嫌丑了...

@zljs
Copy link

zljs commented Jul 23, 2020

我以为我会,我答出来了,过来一看,我还是太年轻了😅你们都是大佬

@jangdelong
Copy link

const list = [1, 2, 3];
const square = num => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}
async function test() {
  for (let x of list) {
    const res = await square(x)
    console.log(res)
  }
}
test()

@MissNanLan
Copy link

MissNanLan commented Oct 14, 2020

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

 function test() {
  list.reduce((prev,next)=>{
    return prev.then(()=> square(next).then(res=>console.log(res)))
  },Promise.resolve())
}
test()

@zhoufs1
Copy link

zhoufs1 commented Oct 22, 2020

const list = [1, 2, 3];

const square = async (num) => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * num);
}, 1000);
});
};

const test = async () => {
for (let x of list) {
const res = await square(x);
console.log(res);
}
};

test();

@Mrcxt
Copy link

Mrcxt commented Oct 27, 2020

以前面试遇到一个差不多的题,当时用的是链式递归实现的:

function test(i = 0) {
        if (i  === list.length) return
        return square(list[i]).then(res => {
            console.log(res);
            return test(++i)
        })
    }
    test()

@JaykeyGuo
Copy link

对于forEach的并行处理,可以用下面这段代码来学习一下:

const count = (item) => {
  return new Promise((resolve) => {
	  if (typeof item === 'number') {
      setTimeout(() => {
        console.log(item);
        resolve();
      }, 500);
    } else {
      setTimeout(() => {
        console.log(item);
        resolve();
      }, 1000);
    }
  })
}

// forEach
let arr = [1, 'a', 'b', 'c', 2];
arr.forEach(async item => {
  const ret = await count(item);
});

// 1
// 2
// a
// b
// c

// for
(async function test() {
  for (let i = 0; i < arr.length; i++) {
    await count(arr[i]);
  }
})()

// for ... of
(async function test() {
  for (let x of arr) {
    const res = await count(x)
  }
})()

@littleebirds
Copy link

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

function test(i=0) {
        console.log('i', i)
      square(list[i]).then(num=>{
        console.log(num)
        if(list[i+1]){
            test(i+1)
         }
      })
}

test()

@JaykeyGuo
Copy link

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

(async function test() {
  for (let x of list) {
    const res = await square(x);
    console.log(res);
  }
})()

@zhouhan1033
Copy link

zhouhan1033 commented Nov 18, 2020

const list = [1, 2, 3]
const square = num => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

function test() {
    test.p = Promise.resolve();
    list.forEach(x => {
        test.p = test.p.then(() => square(x)).then(console.log)
    })
}
test()

@undo03
Copy link

undo03 commented Dec 18, 2020

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

function test() {
  let promise  = Promise.resolve();
  list.forEach(x => {
    promise = promise.then(() => square(x)).then(res => console.log(res))
  })
}
test()

@VagrantDaniel
Copy link

VagrantDaniel commented Dec 18, 2020

第三种方式是有缺陷的,1秒后同时输出1,4,9
给出改进的方法
const list = [1, 2, 3] const square = num => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(num * num) }, 1000) }) } let promise = Promise.resolve(); function test(i = 0) { if (i === list.length) return; promise = promise.then(async () => { await square(list[i]).then(res => { console.log('res', res) }) }); test(i + 1) } test()

@xiaoyangzi
Copy link

直接在forEach 里面套个setTimeOut 不就可以了

function test() {
list.forEach(async x => {
setTimeout(async () => {
const res = await square(x)
console.log(res)
}, 1000 * x)
})
}

这角度相当刁钻了,就是但凡数组不是连续整数这代码还得改。。。应该用index去乘。

@sjhleo
Copy link

sjhleo commented Apr 13, 2021

function test() {
    var p = Promise.resolve();
    list.forEach(x => {
        p = p.then(() => square(x).then(res => console.log(res)));
    });
}

@poppydani
Copy link

function test() { list.reduce(async (p, x)=> { await p; const res = await square(x) console.log(res) }, Promise.resolve()) }

@poppydani
Copy link

function test() {
  list.reduce(async (p, x)=> {
    await p;
    const res = await square(x)
    console.log(res, +new Date() / 1000)
  }, Promise.resolve())
}

@sbzkp
Copy link

sbzkp commented Sep 13, 2021

`const list = [1, 2, 3]
const square = num => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}

function test() {
(async()=>{
for (let index = 0; index < 3; index++) {
const res = await square(index)
console.log(new Date(), res)
}
})()
}
test()
`

@angusjiang
Copy link

改造test就好了

function test() {
  list.forEach(async(x, index)=> {
    const res = await square(x)
    setTimeout(() => {console.log(res)}, 1000*index)
  })
}

@zenghongtu
Copy link

用reduce实现链式调用

const list = [1, 2, 3]
const square = num=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(num * num)
        }
        , 1000)
    }
    )
}

function test() {
    list.reduce((p,x)=>{
        return p.then(async()=>{
            const res = await square(x)
            console.log(res)
        }
        )
    }
    , Promise.resolve())
}
test()

@linsicong003
Copy link

linsicong003 commented Dec 10, 2021 via email

@itLeeyw
Copy link

itLeeyw commented May 19, 2022

重写 forEach

Array.prototype.forEach = async function(callback, thisArg) {
    let bindThis, idx;
    
    if (this === null) {
      throw new TypeError('this is null or not defined');
    }
    
    let arrayObj = Object(this);
    
    let len = O.length >>> 0;
    
    if (typeof callback !== 'function') {
      throw new TypeError(callback + 'is not a function')
    }
    
    if (arguments.length > 1) {
      bindThis = thisArg;
    }
    
    idx = 0
    
    while (idx < len) {
      let item;
      if (idx in arrayObj) {
        item = arrayObj[idx];
        await callback.call(bindThis, item, idx, arrayObj);
      }
      idx++;
    }
  }

arr = new Array(1,2,3);
async function foo(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(n + n);
    }, 1000);
  });
}
arr.forEach(async (item) => {
  console.log(await foo(item));
});

@linsicong003
Copy link

linsicong003 commented May 19, 2022 via email

@lucaslmlok
Copy link

const list = [1, 2, 3];
const square = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num);
    }, 1000);
  });
};

async function test() {
  for (const item of list) {
    const res = await square(item);
    console.log(res);
  }
}
test();

@Yangfan2016
Copy link

for
for of

reduce

function test() {
    list.reduce((acc,cur,index)=> acc.then(()=>square(list[index])),Promise.resolve());
}

@linsicong003
Copy link

linsicong003 commented Aug 18, 2022 via email

@qiqingfu
Copy link

const list = [1, 2, 3];
const square = (num) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num);
    }, 1000);
  });
};

async function test(index) {
  const val = list[index];
  if (val === undefined) return;
  const res = await square(val);
  console.log(res);
  test(index + 1);
}

test(0);

@xiaoguoaa
Copy link

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

function test() {
  list.forEach(async (x, i)=> {
    const res = await square(x)
    setTimeout(() => console.log(res), i * 1000)
  })
}
test()

@linsicong003
Copy link

linsicong003 commented Mar 15, 2023 via email

@zlh-GitHub
Copy link

zlh-GitHub commented May 7, 2024

《ES6 入门》中还提到了使用 reduce 解决:

list.reduce(async (_, x) => {
  await _
  const res = await square(x)
  console.log(res)
}, undefined)

大哥 这里面都没有return 为什么await _ (_) 会是上一次的方法呢 怎么累积的呢 ??? 传统的 不都是这样的吗 var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) { return accumulator + currentValue; }, 0); 能帮忙解答下吗 迷茫。。。。。

list.reduce((_, x) => { console.log(_, x) }, undefined)

_ 是初始值或者上次回调函数的执行结果,如

[3, 2, 1].reduce((_, x) => { console.log(_, x); return `init${x}`; }, 'init')

结果为

init 3
init3 2
init2 1

当加入 async 后上次回调函数的执行结果将变为 pending 状态的 promise
所以回到题解中第一次回调执行,可以看作是

async function firstCb(_, x) {
  await undefined;
  const res = await square(x);
  console.log(res);
}
firstCb(undefined, 1);

第二次回调执行可以看作是

async function secondCb(_, x) {
  await _;
  const res = await square(x);
  console.log(res);
}
secondCb(firstCb(), 2);

在执行 secondCb 的时候,里面会等待 firstCb 的完成,再接着等待 square(x) 的完成,以此类推,所以能实现每隔一秒打印的效果。

@linsicong003
Copy link

linsicong003 commented May 7, 2024 via email

1 similar comment
@linsicong003
Copy link

linsicong003 commented Jun 16, 2024 via email

@chen-ziwen
Copy link

chen-ziwen commented Jun 16, 2024

1秒后同时输出 1, 4, 9。因为forEach方法传入的函数是通过内部的循环去执行,将它写为async函数并不能阻塞循环。想要理解问题的关键点,首先要知道forEach是如何实现的。

forEach的简单实现

Array.prototype.forEach = function (fun) {
    for (let i = 0; i < this.length; i++) {
        fun.call(this, this[i], i, this);  // 此时fun为async函数
    }
} 

改造test:

async function test() {
  for(let x of list) {
    const res = await square(x)
    console.log(res)
  }
}
test() 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests