单线程

  • 原因:

    • 与它的用途有关: 作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。
  • blog/posts/关于JavaScript单线程的一些事.md at master · JChehe/blog

回调函数

  • 最基本的方法

实例

//有两个函数f1和f2,后者等待前者的执行结果。f1耗时长
f1();
f2();

// 可以考虑改写f1,把f2写成f1的回调函数
function f1(callback){
 setTimeout(function () {
  // f1的任务代码
  callback();
 }, 1000);
}

优点

  • 简单、容易理解和部署

缺点

  • 不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱
  • 每个任务只能指定一个回调函数

事件监听

  • 任务的执行不取决于代码的顺序,而取决于某个事件是否发生

示例

// 为f1绑定一个事件
f1.on('done',f2);

// 改写f1
function f1(){
  setTimeout(function () {
    f1.emit('done');
  },1000);  
}

优点

  • 比较容易理解
  • 可以绑定多个事件,每个事件可以指定多个回调函数
  • 可以"去耦合"(Decoupling),有利于实现模块化

缺点

  • 整个程序都要变成事件驱动型,运行流程会变得很不清晰

Promise

描述

  • 一个 Promise 是一个代理,它代表一个在创建 promise 时不一定已知的值。
  • 它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。
  • 这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。

状态

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

方法

  • .then():
    • 最多接受两个参数;第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数
    • 返回一个新生成的 Promise 对象,这个对象可被用于链式调用
  • .catch()其实就是一个没有为 Promise 兑现时的回调函数留出空位的 .then()
  • Promise.all()
    • 特点
      1. 接收一个可迭代对象
      2. 传入的数据中可以是普通数据,也可以是Promise对象
      3. 可迭代对象的promise是并行执行的
      4. 保持输入数组的顺序和输出数组的顺序一致
      5. 传入数组中只要有一个reject,立即返回reject,并带有第一个被拒绝的原因。
      6. 所有数据resolve之后返回一个 Promise
    • 实现: 参考: 手写Promise.all - 掘金 JavaScript function myPromiseAll(iterable){ return new Promise((resolve,reject)=>{ const promises = Array.from(iterable); const result = []; let count = 0; const len = promises.length; for(let i=0;i<len;i++){ Promise.resolve(promises[i]).then(res=>{ result[i] = res; count++; if(count===len){ resolve(result); } }).catch(err=>{ reject(err); }) } }) }
  • Promise.any()
    • 当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,它会以一个包含拒绝原因数组的 AggregateError 拒绝。
  • Promise.allSettld()
  • Promise.race()
    • 特点:
      1. 返回第一个执行完成的 Promise,无论它是被 resolve 还是 reject
    • 实现:
      function myPromiseRace(promises){
          return new Promise((resolve,reject)=>{
              for (let promise of promises){
                  Promise.resolve(promise).then(resolve,reject);
              }
          })
      }
      
  • Promise.withResolvers()

作用

缺点

  1. 一旦新建它就会立即执行,无法中途取消
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
  3. 当处于Pending状态时,无法得知目前进展到哪一个阶段

问题一:与setTimeout的区别

  • setTimeout处于宏任务队列,Promise位于微任务队列
  • 示例
// 用一个已解决的 Promise——“resolvedProm”为例,
// 函数调用“resolvedProm.then(...)”立即返回一个新的 Promise,
// 但是其中的处理器“(value) => {...}”将被异步调用,正如打印输出所示。
// 新的 Promise 被赋值给“thenProm”,
// 并且 thenProm 将被解决为处理函数返回的值。
const resolvedProm = Promise.resolve(33);
console.log(resolvedProm);

const thenProm = resolvedProm.then((value) => {
  console.log(
    `在主堆栈结束后被调用。收到的值是:${value},返回的值是:${value + 1}`,
  );
  return value + 1;
});
console.log(thenProm);

// 使用 setTimeout,我们可以将函数的执行推迟到调用栈为空的时刻。
setTimeout(() => {
  console.log(thenProm);
});

// 按顺序打印:
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 33}
// Promise {[[PromiseStatus]]: "pending", [[PromiseResult]]: undefined}
// "在主堆栈结束后被调用。收到的值是:33,返回的值是:34"
// Promise {[[PromiseStatus]]: "resolved", [[PromiseResult]]: 34}

问题二: Promise的原理

  • 观察者模式 wip

问题二: Promise/A+ 规范

wip

参考


async/await

作用

  • 函数前面的关键字async有两个作用:
    1. 让这个函数总是返回一个 promise
      • 返回普通值时将自动被包装在一个 resolved 的 promise 中。
      • 也可以显式地返回一个 promise
    2. 允许在该函数内使用 await。
  • Promise 前的关键字 await
    • 当 JavaScript 执行到 await 时,它会等待 Promise 的结果,
    • 但这个等待过程不会阻塞主线程。JavaScript 引擎会将这个等待中的任务挂起,并允许继续处理其他同步代码或事件,直到 Promise 完成(或者超时、拒绝)。
    • 这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。

实现

wip

如何处理错误

wip

示例

async function f() {
  try {
    let results = await Promise.all([
      fetch(url1),
      fetch(url2)
    ]);
  } catch(err) {
    alert(err);
  }
}

f();
const arr = [];

async function add(arr, item) {
    await Promise.resolve();
    arr.push(item);
}

(async () => {
console.log('arr in IIFE1', arr.toString());
await add(arr, 2);
console.log('arr in IIFE2', arr.toString());
})()

add(arr, 1); 

(async () => {
console.log('arr in IIFE3', arr.toString());
await add(arr, 3);
console.log('arr in IIFE4', arr.toString());
})()

console.log('arr', arr.toString());

setTimeout(()=>{
  console.log('arr in setTimeout', arr.toString());
},1000);

// arr in IIFE1
// arr in IIFE3
// arr
// arr in IIFE2 2,1,3
// arr in IIFE4 2,1,3
// arr in setTimeout 2,1,3

参考


异步请求的竞态问题

场景一

  • 描述:需多次请求某一api,响应时间不固定。需要渲染的是最后一个请求返回的结果;常见于搜索,分页,选项卡等切换的场景
  • 方法一:忽略: 计数,然后判断
  • 方法二:取消: 在连续的请求过程中,每当我发出一个请求,我就将之前正在 pending 的请求的 Promise reject 掉,并且该请求的 XHR 对象执行 abort();之前的请求 如果已经有响应的不用管它,我们当前的请求的结果会覆盖它的

参考:


执行顺序

参考