小宋爱睡觉 小宋爱睡觉
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
  • 手写系列
  • Promise
    • 实现一个详细版Promise
    • 实现Promise.resolve
    • 实现Promise.reject
    • 实现 Promise.finally
    • 实现 Promise.all
    • 实现promise.allsettle
    • 实现 Promise.race
    • 实现Promise.any
    • 使用Promise实现每隔1秒输出1,2,3
    • 循环打印红黄绿
    • 实现mergePromise函数
    • 用promise实现图片异步加载
    • 封装异步的fetch使用async await方式来使用
    • 实现一个带并发限制的异步调度器 Scheduler
    • 实现cacheRequest(),相同资源ajax只发一次请求
    • 手写一个singlePipe实现一个任务没有完成,下一个任务不响应
    • 实现 Promise.retry,成功后resolve,可延迟重试n次,超过n次数后reject
    • Promise延迟执行
    • 输出结果;如果希望每隔 1s 输出一个结果,应该如何改造?注意不可改动 square 方法
    • 串行执行 task
    • 链式调用
  • 场景应用
  • 数组的方法
  • 正则表达式相关
  • Vue手写
  • 手写系列
Crucials
2022-01-12

Promise

# 实现一个详细版Promise

class MyPromise {
  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';
  static REJECTED = 'rejected';

  constructor(executor) {
    this.state = MyPromise.PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === MyPromise.PENDING) {
        this.state = MyPromise.FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === MyPromise.PENDING) {
        this.state = MyPromise.REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };

    return new MyPromise((resolve, reject) => {
      const process = (callback, value, resolve, reject) => {
        try {
          const x = callback(value);
          if (x instanceof MyPromise) {
            x.then(resolve, reject);
          } else {
            resolve(x);
          }
        } catch (err) {
          reject(err);
        }
      };

      if (this.state === MyPromise.FULFILLED) {
        setTimeout(() => process(onFulfilled, this.value, resolve, reject));
      } else if (this.state === MyPromise.REJECTED) {
        setTimeout(() => process(onRejected, this.reason, resolve, reject));
      } else {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => process(onFulfilled, this.value, resolve, reject));
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => process(onRejected, this.reason, resolve, reject));
        });
      }
    });
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(callback) {
    return this.then(
      val => MyPromise.resolve(callback()).then(() => val),
      err => MyPromise.resolve(callback()).then(() => { throw err; })
    );
  }

  static resolve(value) {
    return new MyPromise((resolve) => resolve(value));
  }

  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

# 实现Promise.resolve

实现 resolve 静态方法有三个要点:

  • 传参为一个 Promise, 则直接返回它。
  • 传参为一个 thenable 对象,返回的 Promise 会跟随这个对象,采用它的最终状态作为自己的状态。
  • 其他情况,直接返回以该值为成功状态的promise对象。
Promise.resolve = (param) => {
  if(param instanceof Promise) return param;
  return new Promise((resolve, reject) => {
    if(param && param.then && typeof param.then === 'function') {
      // param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
      param.then(resolve, reject);
    }else {
      resolve(param);
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11

# 实现Promise.reject

Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传, 实现如下:

Promise.reject = function (reason) {
    return new Promise((resolve, reject) => {
        reject(reason);
    });
}
1
2
3
4
5

# 实现 Promise.finally

无论当前 Promise 是成功还是失败,调用finally之后都会执行 finally 中传入的函数,并且将值原封不动的往下传。

// 借用 Promise.resolve方法,使得 callback执行完返回后才会继续向下,
// 将 promise对象的原数据继续向下传递,
// 失败数据则需要抛出供后续catch 使用
Promise.prototype.finally = function(cb) {
	return this.then(value => {
    cb()
    return value
  }, err => {
    cb()
    throw err
  })
}
1
2
3
4
5
6
7
8
9
10
11
12

# 实现 Promise.all 重点

对于 all 方法而言,需要完成下面的核心功能:

接收一个promise数组,返回一个新promise2,并发执行数组中的全部promise

  • 传入参数为一个空的可迭代对象,则直接进行resolve。
  • 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败。
  • 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
Promise.all = function(promises) {
  return new Promise((resolve, reject) => {
    let result = [];
    let index = 0;
    let len = promises.length;
    if(len === 0) {
      resolve(result);
      return;
    }
   
    for(let i = 0; i < len; i++) {
      // 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
      Promise.resolve(promise[i]).then(data => {
        result[i] = data;
        index++;
        if(index === len) resolve(result);
      }).catch(err => {
        reject(err);
      })
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 实现promise.allsettle

接受的结果与入参时的promise实例一一对应,且结果的每一项都是一个对象,告诉你结果和值,对象内都有一个属性叫“status”,用来明确知道对应的这个promise实例的状态(fulfilled或rejected),fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]
1
2
3
4
5
6
7
8
9
10
11
12

重要的一点是,他不论接受入参的promise本身的状态,会返回所有promise的结果,但这一点Promise.all做不到,如果你需要知道所有入参的异步操作的所有结果,或者需要知道这些异步操作是否全部结束,应该使用promise.allSettled()

实现

function allSettled(iterable) {
  return new Promise((resolve, reject) => {
    function addElementToResult(i, elem) {
      result[i] = elem;
      elementCount++;
      if (elementCount === result.length) {
        resolve(result);
      }
    }

    let index = 0;
    for (const promise of iterable) {
      // Capture the current value of `index`
      const currentIndex = index;
      promise.then(
        (value) => addElementToResult(
          currentIndex, {
            status: 'fulfilled',
            value
          }),
        (reason) => addElementToResult(
          currentIndex, {
            status: 'rejected',
            reason
          }));
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    const result = new Array(index);
  });
}


// 借助Promise.all实现
if (!Promise.allSettled) {
  const rejectHandler = (reason) => ({ status: "rejected", reason });
  const resolveHandler = (value) => ({ status: "fulfilled", value });
  Promise.allSettled = (promises) =>
    Promise.all(
      promises.map((promise) =>
        Promise.resolve(promise).then(resolveHandler, rejectHandler)
      )
      // 每个 promise 需要用 Promise.resolve 包裹下
      // 以防传递非 promise
    );
}

// 使用
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise((resolve, reject) => {
  setTimeout(reject, 1000, "three");
});
const promises = [p1, p2, p3];
Promise.allSettled(promises).then(console.log);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 实现 Promise.race 重点

race 的实现相比之下就简单一些,只要有一个 promise 执行完,直接 resolve 并停止执行

Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (const p of promises) {
      p.then((value) => {
        resolve(value);
      }, reject);
    }
  });
};
1
2
3
4
5
6
7
8
9

# 实现Promise.any

Promise.any = function(promises) {
  return new Promise((resolve, reject) => {
    let count = 0
    promises.forEach((promise) => {
      promise.then(val => {
        resolve(val)
      }, err => {
        count++
        if (count === promises.length) {
          reject(new AggregateError('All promises were rejected'))
        }
      })
    })
  })
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 使用Promise实现每隔1秒输出1,2,3

const arr = [1, 2, 3];
arr.reduce(
  (p, x) =>
    p.then(() => new Promise((r) => setTimeout(() => r(console.log(x)), 1000))),
  Promise.resolve()
);
1
2
3
4
5
6

改造一下改成一秒后按顺序输出1,2,3

const arr = [1, 2, 3];
arr.reduce(
  (p, x) =>
    p.then(new Promise((r) => setTimeout(() => r(console.log(x)), 1000))),
  Promise.resolve()
);
// 等价于
Promise.resolve()
  .then(() => {
    return new Promise(r => {
      setTimeout(() => {
        r(console.log(1))
      }, 1000)
    })
  })
  .then(r => {
    return new Promise(r => {
      setTimeout(() => {
        r(console.log(2))
      }, 1000)
    })
  })
  .then(r => {
    return new Promise(r => {
      setTimeout(() => {
        r(console.log(3))
      }, 1000)
    })
  })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

p .then里面的() => new Promise改成 new Promise

# 循环打印红黄绿

红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?

三个亮灯函数:

function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}
const task = (time, action) =>
	new Promise((resolve) => {
    setTimeout(() => {
      action();
      resolve();
    }, time);
});

const taskRunner =  async () => {
    await task(3000, red)
    await task(2000, green)
    await task(2100, yellow)
    taskRunner()
}
taskRunner()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 实现mergePromise函数

实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。

const time = (timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, timer)
  })
}
const ajax1 = () => time(2000).then(() => {
  console.log(1);
  return 1
})
const ajax2 = () => time(1000).then(() => {
  console.log(2);
  return 2
})
const ajax3 = () => time(1000).then(() => {
  console.log(3);
  return 3
})

function mergePromise () {
  // 在这里写代码
}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
  console.log("done");
  console.log(data); // data 为 [1, 2, 3]
});

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

这道题有点类似于Promise.all(),不过.all()不需要管执行顺序,只需要并发执行就行了。但是这里需要等上一个执行完毕之后才能执行下一个。

答案

function mergePromise (ajaxArray) {
  // 存放每个ajax的结果
  const data = [];
  let promise = Promise.resolve();
  ajaxArray.forEach(ajax => {
  	// 第一次的then为了用来调用ajax
  	// 第二次的then是为了获取ajax的结果
    promise = promise.then(ajax).then(res => {
      data.push(res);
      return data; // 把每次的结果返回
    })
  })
  // 最后得到的promise它的值就是data
  return promise;
}

// async版
const mergePromise = (ajaxArray) => {
  // 在这里实现你的代码
  return new Promise(async function (resolve) {
    let data = [];
    for (let item of ajaxArray) {
      let tmp = await item();
      data.push(tmp);
    }
    resolve(data);
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 用promise实现图片异步加载

let imageAsync = (url) => {
  return new Promise((resolve, reject) => {
    let image = new Image();
    image.onload = function () {
      img.src = url;
      resolve(image);
    };
    image.onerror = function (err) {
      reject(err);
    };
  });
};

imageAsync("url")
  .then((img) => {
    console.log("image加载成功");
  })
  .catch((err) => {
    console.log(err);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 封装异步的fetch使用async await方式来使用

(async () => {
    class HttpRequestUtil {
        async get(url) {
            const res = await fetch(url);
            const data = await res.json();
            return data;
        }
        async post(url, data) {
            const res = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async put(url, data) {
            const res = await fetch(url, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async delete(url, data) {
            const res = await fetch(url, {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json'
                },
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
    }
    const httpRequestUtil = new HttpRequestUtil();
    const res = await httpRequestUtil.get('www.crucials.cn');
    console.log(res);
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 实现一个带并发限制的异步调度器 Scheduler 重要

实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个。完善下面代码中的 Scheduler 类,使得以下程序能正确输出。

class Scheduler {
  add(promiseCreator) { ... }
  // ...
}

const timeout = (time) => new Promise(resolve => {
  setTimeout(resolve, time)
})

const scheduler = new Scheduler()
const addTask = (time, order) => {
  scheduler.add(() => timeout(time)).then(() => console.log(order))
}

addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')

// 打印顺序是:2 3 1 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Scheduler {
  private limit: number;
  private curIdx: number = 0;
  private queue: (() => Promise<void>)[] = [];

  constructor(limit = 2) {
    this.limit = limit;
  }

  add(fn: () => Promise<void>) {
    const inQueueFn = async () => {
      this.curIdx++;
      await fn();
      this.curIdx--;
      this.next();
    };
    this.queue.push(inQueueFn);
  }

  async next() {
    if (!this.queue.length || this.curIdx >= this.limit) {
      return;
    }

    const task = this.queue.shift();
    await task?.();
  }

  start() {
    for(let i = 0; i < this.limit; i++) {
      this.next();
    }
  }
}
const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });

const scheduler = new Scheduler();
const addTask = (time, order) => {
  scheduler.add(() => timeout(time)).then(() => console.log(order));
};

addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

题目下面是没有scheduler.start();修改一下

class Scheduler {
  constructor() {
    this.taskPool = [];
    this.limit = 2;
  }
  add(promiseCreator) {
    return new Promise((resolve, reject) => {
      this.taskPool.push(async () => {
        await promiseCreator();
        resolve();
      });
    setTimeout(this.start.bind(this));
    });
  }
  start() {
    while (this.limit-- > 0) {
      this.next();
    }
  }

  async next() {
    if (this.taskPool.length) {
      await this.taskPool.shift()();
      this.next();
    }
  }
}

const timeout = (time) =>
  new Promise((resolve) => {
    setTimeout(resolve, time);
  });

const scheduler = new Scheduler();
const addTask = (time, order) => {
  scheduler.add(() => timeout(time)).then(() => console.log(order));
};

addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 实现cacheRequest(),相同资源ajax只发一次请求

请实现一个cacheRequest方法,保证当前ajax请求相同资源时,真实网络层中,实际只发出一次请求(假设已经存在request方法用于封装ajax请求)

// 构建Map,用作缓存数据
const dict = new Map()
// 这里简单的把url作为cacheKey
const cacheRquest = (url) => {
  if (dict.has(url)) {
    return Promise.resolve(dict.get(url))
  } else {
    // 无缓存,发起真实请求,成功后写入缓存
    return request(url).then(res => {
      dict.set(url, res)
      return res
    }).catch(err => Promise.reject(err))
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

有这么一个小概率边缘情况,并发两个或多个相同资源请求时,第一个请求处于pending的情况下,实际上后面的请求依然会发起,不完全符合题意

可以将promise缓存起来,然后如果处在pending状态的时候就拿出来继续请求

const dict = new Map();
const cacheRquest = (url) => {
  if (dict.has(url)) {
    return dict.get(url)
  } else {
    const pendingData = request(url)
    .then((res) => {
      return res;
    })
    .catch((err) => Promise.reject(err));
    dict.set(url, pendingData)
    return pendingData
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 手写一个singlePipe实现一个任务没有完成,下一个任务不响应

function singlePipe(promiseFunc) {

}

const promiseFunc = (value) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, 1000);
  });
const request = singlePipe(promiseFunc);

request(1).then((value) => console.log(value));
request(2).then((value) => console.log(value));

setTimeout(() => request(3).then((value) => console.log(value)), 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

类似上面的那道带并发的异步调度器,我们把class改造成function


function singlePipe(promiseFunc) {
  this.queue = this.queue || [];
  this.limit = 1;
  this.start = () => {
    while (this.limit-- > 0) {
      this.next();
    }
  };
  this.next = async () => {
    if (this.queue.length) {
      await this.queue.shift()();
      this.next();
    }
  };
  // 返回一个函数,利用了单例模式,让每一个promiseFunc推进的是同一个数组 queue
  return (value) => {
    return new Promise((resolve) => {
      this.queue.push(async () => {
        const res = await promiseFunc(value);
        resolve(res);
      });
      setTimeout(this.start.bind(this));
    });
  };
}

const promiseFunc = (value) =>
  new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, 1000);
  });
const request = singlePipe(promiseFunc);

request(1).then((value) => console.log(value));
request(2).then((value) => console.log(value));

setTimeout(() => request(3).then((value) => console.log(value)), 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 实现 Promise.retry,成功后resolve,可延迟重试n次,超过n次数后reject

const isFunction = function (fn) {
  return Object.prototype.toString.call(fn).slice(8, -1) === "Function";
};

Promise.retry = (fn, options = {}) => {
  if (!isFunction(fn)) throw new Error("fn is not a function");

  const { max = 3, delay = 0 } = options;
  let curMax = max;
  const delayExec = () =>
    delay && new Promise((resolve) => setTimeout(resolve, delay));

  return new Promise(async (resolve, reject) => {
    while (curMax > 0) {
      try {
        const res = await fn();
        resolve(res);
        return;
      } catch (error) {
        await delayExec();
        curMax--;
        console.warn(`剩余次数${curMax}`);
        if (!curMax) reject(error);
      }
    }
  });
};
const resolveData = () =>
  new Promise((resolve, reject) => {
    setTimeout(
      () => (Math.random() > 0.5 ? resolve("成功") : reject(new Error("失败"))),
      1000
    );
  });

(async () => {
  try {
    const res = await Promise.retry(resolveData, { delay: 1000 });
    console.warn("result", res);
  } catch (error) {
    console.warn(error);
  }
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# Promise延迟执行

function job(){      
  return function(){}
}  
let myTodo = job(30000, 5)  
myTodo('alert')
1
2
3
4
5
function job(delay, time) {
  return function (word) {
    const delayPrint = () =>
      new Promise((res) =>
        setTimeout(() => {
          res(console.log(word));
        }, delay)
      );
    let promise = new Promise(async (resolve, reject) => {
      while (time--) {
        try {
          await delayPrint();
          resolve();
        } catch (error) {
          if (!time) {
            reject(error);
          }
        }
      }
    });
    return promise;
  };
}
let myTodo = job(1000, 5);
myTodo("alert");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 输出结果;如果希望每隔 1s 输出一个结果,应该如何改造?注意不可改动 square 方法

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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

1s 后同时打印 1、4、9

原因:使用 promise 或 async 函数作为 forEach() 等类似方法的 callback 参数并不会等待异步的执行

如果我们希望每隔 1s 输出一个结果,方法有:

  • 一个简单的 for 循环
  • for…of / for…in 循环
  • 利用 promise 的链式调用

解法一:for 循环

async function test() {
  for(let i = 0; i<list.length; i++) {
    const res = await square(list[i])
    console.log(res)
  }
}
test()
1
2
3
4
5
6
7

解法二:for…in / for…of

// for...in
async function test() {
  for(let i in list) {
    const res = await square(list[i])
    console.log(res)
  }
}
test()
// for...of
async function test() {
  for(let x of list) {
    const res = await square(x)
    console.log(res)
  }
}
test()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

解法三:利用 promise 的链式调用

function test() {
  let promise = Promise.resolve()
  list.forEach(x=> {
    promise = promise.then(() => square(x)).then(console.log)
  })
}
test()
1
2
3
4
5
6
7

# 串行执行 task

tasks等于[task1,task2, task3],写一个excute函数串行执 行tasks,入参是tasks, timeout, retries. 要求每个任务执行成功则返回一个 promise对象,执行失败则重新执行,执行最大次数为retries,超过 最大次数仍未执行成功,则抛出异 常,如果重试过程中超时同样抛出异 常。

function execute(tasks, timeout, retries) {
  // 包装一个任务,支持重试和超时
  const runWithRetryAndTimeout = async (task, timeout, retries) => {
    let attempt = 0;

    while (attempt <= retries) {
      try {
        // 创建一个超时 Promise
        const timeoutPromise = new Promise((_, reject) =>
          setTimeout(() => reject(new Error('Task timed out')), timeout)
        );

        // Promise.race: 谁先完成就用谁的结果
        const result = await Promise.race([
          task(),
          timeoutPromise,
        ]);

        return result; // 成功则返回
      } catch (err) {
        attempt++;
        if (attempt > retries) {
          throw new Error(`Task failed after ${retries} retries: ${err.message}`);
        }
      }
    }
  };

  // 串行执行
  return (async () => {
    const results = [];
    for (const task of tasks) {
      const result = await runWithRetryAndTimeout(task, timeout, retries);
      results.push(result);
    }
    return results;
  })();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 链式调用

HardMan("Jack")
// => 输出:I am Jack

HardMan("Jack").rest(10).learn("computer")
// => I am Jack
// => 10秒后
// => After 10 seconds
// => Learning computer

HardMan("Jack").restFirst(5).learn("Math")
// => I am Jack
// => 5秒后
// => After 5 seconds
// => Learning Math
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function HardMan(name) {
  return new _HardMan(name);
}

class _HardMan {
  constructor(name) {
    this.tasks = [];

    // 初始任务
    this.tasks.push(() => {
      console.log(`I am ${name}`);
      return Promise.resolve();
    });

    // 延迟启动任务队列(确保链式调用注册完成)
    setTimeout(() => this.runTasks(), 0);
  }

  runTasks() {
    let seq = Promise.resolve();
    for (const task of this.tasks) {
      seq = seq.then(() => task());
    }
  }

  rest(seconds) {
    this.tasks.push(() => {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log(`After ${seconds} seconds`);
          resolve();
        }, seconds * 1000);
      });
    });
    return this;
  }

  restFirst(seconds) {
    this.tasks.unshift(() => {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log(`After ${seconds} seconds`);
          resolve();
        }, seconds * 1000);
      });
    });
    return this;
  }

  learn(subject) {
    this.tasks.push(() => {
      console.log(`Learning ${subject}`);
      return Promise.resolve();
    });
    return this;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
上次更新: 2025/05/28, 09:28:27
手写系列
场景应用

← 手写系列 场景应用→

Copyright © 2021-2025 粤ICP备2021165371号
  • 跟随系统
  • 浅色模式
  • 深色模式