一, 背景
react 项目中, 渲染组件时, 显示的数据一直有问题, 本来以为是 react 组件的问题, 后来才发现罪魁祸首在 fetch 数据的过程, 因为我用了 async/await , 而却搭配了 foreach 去循环拉取数据, 却导致本以为是同步的操作还是变成了异步.
二, 正文
沿用我之前一篇文章 (callback vs async.JS vs promise vs async / await) 里的例子, 来重现这个错误:
- let read = function (code) {
- if (code) {
- return true;
- } else {
- return false;
- }
- }
- let readFileA = function () {
- return new Promise(function (resolve, reject) {
- if (read(1)) {
- resolve("111");
- } else {
- reject("a fail");
- }
- });
- }
- let readFileB = function () {
- return new Promise(function (resolve, reject) {
- if (read(1)) {
- resolve("222");
- } else {
- reject("b fail");
- }
- });
- }
- let readFileC = function () {
- return new Promise(function (resolve, reject) {
- if (read(1)) {
- resolve("333");
- } else {
- reject("c fail");
- }
- });
- }
- async function test() {
- try {
- let readFileFun = [readFileA(), readFileB(), readFileC()]
- console.log("..................start..................")
- // // 方法一: forEach
- // await readFileFun.forEach(async (func, i) => {
- // console.log("start:", i+1)
- // let re = await func;
- // console.log(re)
- // console.log("end:", i+1)
- // })
- // // 方法二: for loop
- // for (let i = 0; i <readFileFun.length; ++i) {
- // console.log("start:", i+1)
- // let re = await readFileFun[i];
- // console.log(re)
- // console.log("end:", i+1)
- // }
- // // 方法三: for ... of
- // for (const [i, func] of readFileFun.entries()) {
- // console.log("start:", i+1)
- // let re = await func;
- // console.log(re)
- // console.log("end:", i+1)
- // }
- console.log("..................end..................")
- } catch (err) {
- console.log(err); // 如果 b 失败, return: b fail
- }
- }
- test();
输出结果:
- # (错)方法一:
- ..................start..................
- start: 1
- start: 2
- start: 3
- 111
- end: 1
- 222
- end: 2
- 333
- end: 3
- ..................end..................
- # (对)方法二, 三:
- ..................start..................
- start: 1
- 111
- end: 1
- start: 2
- 222
- end: 2
- start: 3
- 333
- end: 3
- ..................end..................
为什么 foreach 不行, 而 普通 for 循环 和 for...of 却正常呢?
我们得先从 foreach 的源码看起:(>)
- // Production steps of ECMA-262, Edition 5, 15.4.4.18
- // Reference: http://es5.github.io/#x15.4.4.18
- if (!Array.prototype.forEach) {
- Array.prototype.forEach = function(callback/*, thisArg*/) {
- var T, k;
- if (this == null) {
- throw new TypeError('this is null or not defined');
- }
- // 1. Let O be the result of calling toObject() passing the
- // |this| value as the argument.
- var O = Object(this);
- // 2. Let lenValue be the result of calling the Get() internal
- // method of O with the argument "length".
- // 3. Let len be toUint32(lenValue).
- var len = O.length>>> 0;
- // 4. If isCallable(callback) is false, throw a TypeError exception.
- // See: http://es5.github.com/#x9.11
- if (typeof callback !== 'function') {
- throw new TypeError(callback + 'is not a function');
- }
- // 5. If thisArg was supplied, let T be thisArg; else let
- // T be undefined.
- if (arguments.length> 1) {
- T = arguments[1];
- }
- // 6. Let k be 0.
- k = 0;
- // 7. Repeat while k < len.
- while (k < len) {
- var kValue;
- // a. Let Pk be ToString(k).
- // This is implicit for LHS operands of the in operator.
- // b. Let kPresent be the result of calling the HasProperty
- // internal method of O with argument Pk.
- // This step can be combined with c.
- // c. If kPresent is true, then
- if (k in O) {
- // i. Let kValue be the result of calling the Get internal
- // method of O with argument Pk.
- kValue = O[k];
- // ii. Call the Call internal method of callback with T as
- // the this value and argument list containing kValue, k, and O.
- callback.call(T, kValue, k, O);
- }
- // d. Increase k by 1.
- k++;
- }
- // 8. return undefined.
- };
- }
摘抄最重要的部分:
- /*
- O 为传入数组
- len 为传入数组长度
- callback 为传入回调函数
- */
- while (k < len) {
- var kValue;
- if (k in O) {
- kValue = O[k];
- callback.call(T, kValue, k, O);
- }
- k++;
- }
可以看到 callback.call(T, kValue, k, O); 这一句, callback 其实是我们传入的一个被 async 封装的 promise 对象, 而 Array.prototype.forEach 内部并未对这个 promise 对象做任何处理, 只是忽略它.
如果我们尝试把 Array.prototype.forEach 改造一下, 让它不要忽视, 就可以达到效果了, 如下:
- Array.prototype.forEach = async function(callback/*, thisArg*/) {
- // .........
- await callback.call(T, kValue, k, O);
- // .........
- };
解决方案
你总不能去侵入式的改造 Array.prototype.forEach 吧! 所以最简单的办法就是抛弃 foreach, 使用 for...of 或者 for 循环!
参考资料
https://github.com/babel/babel/issues/909
来源: https://www.cnblogs.com/xjnotxj/p/10629900.html