【JS Promise】使用promise一定要注意的几个问题

读完此文对promise一定会有新的认识。

  1. promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败);
  2. promise状态改变只有两种可能:从pending变为fulfilled和从pending变为rejected。状态一旦改变,就不会再变;
  3. 为了方便描述,一般将fulfilled状态称为resolved

1. 如何改变promise的状态?

  • (1) resolve(value):如果当前是pending,则变为resolved
const p1 = new Promise((resolve, reject) => {
  resolve('success');
});

console.log(p1); // Promise {<fulfilled>: 'success'}
  • (2) reject(reason):如果当前是pending,则变为rejected
const p2 = new Promise((resolve, reject) => {
  reject('err');
});

console.log(p2); // Promise {<rejected>: 'err'}
  • (3) 抛出异常:如果当前是pending,则变为rejected
const p3 = new Promise((resolve, reject) => {
  throw new Error('err');
});

console,.log(p3) // Promise {<rejected>: Error: err

总结:改变promise的状态有三种方式(调用resolve、调用reject和抛出异常)。

2. 一个promise同时指定多个成功/失败回调,都会调用吗?

  • 成功的情况
const p = new Promise((resolve, reject) => {
  resolve('success');
});

p.then((value) => {
  console.log('value1:', value);
});
p.then((value) => {
  console.log('value2:', value);
});
p.then((value) => {
  console.log('value3:', value);
});

/* 执行结果:
value1: success
value2: success
value3: success
*/
  • 失败的情况
const p = new Promise((resolve, reject) => {
  reject('err');
});

p.then(
  (value) => {},
  (reason) => {
    console.log('reason:1', reason);
  },
);
p.then(
  (value) => {},
  (reason) => {
    console.log('reason:2', reason);
  },
);
p.then(
  (value) => {},
  (reason) => {
    console.log('reason:3', reason);
  },
);

/* 执行结果:
reason:1 err
reason:2 err
reason:3 err
*/

总结:一个promise同时指定多个成功/失败回调函数,当promise改变为对应的状态时都会调用。

3. promise的状态改变和回调函数的指定谁先谁后?

我们知道promise在状态改变时会调用对应的回调函数,但在不同的情况下两种还是有先后顺序的。

  • 先指定回调函数,后改变状态的情况:
const p = new Promise((resolve, reject) => {
  // 执行异步任务,这里用setTimeout模拟,实际上可以是ajax请求
  setTimeout(() => {
    resolve('success'); // 2. 后改变状态(同时指定数据),异步执行回调函数
  }, 1000);
});

p.then((value) => { // 1. 先指定回调函数,保存当前指定的回调函数(代码底层)
  console.log('value:', value);
});

  • 先改变状态,后指定回调函数的情况:
const p = new Promise((resolve, reject) => {
  resolve('success'); // 1. 先改变状态(同时指定数据)
});

p.then((value) => { // 2. 后指定回调函数,异步执行回调函数
  console.log('value:', value);
});

或者:

const p = new Promise((resolve, reject) => {
  // 执行异步任务,这里用setTimeout模拟,实际上可以是ajax请求
  setTimeout(() => {
    resolve('success'); // 1. 先改变状态(同时指定数据)
  }, 1000);
});

setTimeout(() => {
  p.then((value) => { // 2. 后指定回调函数,异步执行回调函数
    console.log('value:', value);
  });
}, 1001); // 时间比上面晚一点即可

总结:

  • 两者都有可能,一般情况(执行异步任务)时先指定回调再改变状态;
  • 如何先改变状态再指定回调函数?
    • 在执行器中直接调用resolve/reject
    • 延迟更长时间再调用then
  • 什么时候才得到数据?
    • 若先指定回调,那当状态改变时,回调函数就会执行,从而得到数据
    • 若先改变状态,那当指定回调时,回调函数就会执行,从而得到数据

4. promise.then 返回的新promise的结果状态由什么决定?

我们知道promise是支持链式调用的,比如下面的情况:

const p = new Promise((resolve, reject) => {
  resolve('success');
});

p.then(
  (value) => {
    console.log('value1:', value);
  },
  (reason) => {
    console.log('reason1:', reason);
  },
)
  .then(
    (value) => {
      console.log('value2:', value);
    },
    (reason) => {
      console.log('reason2:', reason);
    },
  )
  .then(
    (value) => {
      console.log('value3:', value);
    },
    (reason) => {
      console.log('reason3:', reason);
    },
  );


/* 执行结果:
value1: success
value2: undefined
value3: undefined
/*

为什么后面两个回调的执行结果是undefined呢,因为新的promise的结果状态是由then()指定的回调函数执行的结果决定的。可以看到第一个then方法中回调函数没有指定返回值,那么就会默认返回undefined,从而导致后面的then方法的回调函数的执行结果都为undefined

那要想后面两个then方法的回调函数的执行结果有值,可以做如下修改:

p.then(
  (value) => {
    console.log('value1:', value);
+    return 'hello';
  },
  (reason) => {
    console.log('reason1:', reason);
  },
)
  .then(
    (value) => {
      console.log('value2:', value);
 +     return Promise.resolve('world');
    },
    (reason) => {
      console.log('reason2:', reason);
    },
  )
  .then(
    (value) => {
      console.log('value3:', value);
    },
    (reason) => {
      console.log('reason3:', reason);
    },
  );

/* 执行结果:
value1: success
value2: hello
value3: world
/*

体会一下下面代码的输出结果:

const p = new Promise((resolve, reject) => {
  reject('err');
});

p.then(
  (value) => {
    console.log('value1:', value);
    return 'hello';
  },
  (reason) => {
    console.log('reason1:', reason);
    throw 'err2';
  },
)
  .then(
    (value) => {
      console.log('value2:', value);
      return Promise.resolve('world');
    },
    (reason) => {
      console.log('reason2:', reason);
      return 1000;
    },
  )
  .then(
    (value) => {
      console.log('value3:', value);
    },
    (reason) => {
      console.log('reason3:', reason);
    },
  );
/* 执行结果:
reason1: err
reason2: err2
value3: 1000
*

总结:新的promise(记为promise1)的结果状态:

  • 简单来说:由then()指定的回调函数执行的结果决定。
  • 详细来讲:
    • 若抛出异常:promise1变为rejected ,reason的值为抛出的异常;
    • 若返回的是非promise的任意值:promise1变为resolved ,value的值为返回的任意值;
    • 若返回的是另一个新的promise:此promise的结果就会成为promise1的结果。

5. promise如何串联多个任务?

  • promise.then()会返回一个新的promise,可以写成then()的链式调用;
  • 通过then()的链式调用来串联多个同步/异步任务。

看下面这段代码:

new Promise((resolve, reject) => {
  // 模拟异步任务
  setTimeout(() => {
    console.log('执行任务1(这是异步的)');
    resolve(100);
  }, 1000);
})
  .then((value) => {
    console.log('任务1的返回结果:', value);

    console.log('执行任务2(这是同步的)');
    return Promise.resolve(200);
  })
  .then((value) => {
    console.log('任务2的返回结果:', value);

    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('执行任务3(这是异步的)');
        resolve(300);
      }, 1000);
    });
  })
  .then((value) => {
    console.log('任务3的返回结果:', value);
  });

/* 执行结果:
执行任务1(这是异步的)
任务1的返回结果: 100
执行任务2(这是同步的)
任务2的返回结果: 200
执行任务3(这是异步的)
任务3的返回结果: 300
*/

可以看到3个任务不论同步还是异步,都是按顺序执行的。

需要注意的是:若想在then()中继续执行异步任务,则用promise包装,在其中调用新promise的resolve方法来讲值传递出去。

6. promise的异常穿透

  • 当使用then链式调用时,可以在最后在指定失败的回调;
  • 当前面任何操作出了异常,都会传到最后失败的回调中处理。
new Promise((resolve, reject) => {
  reject('err');
})
  .then((value) => {
    console.log('value1:', value);
  })
  .then((value) => {
    console.log('value2:', value);
  })
  .then((value) => {
    console.log('value3:', value);
  })
  .catch((reason) => {
    console.log('reason:', reason);
  });

/* 执行结果:
reason: err
*/

上面的这段代码其实相当于:

new Promise((resolve, reject) => {
  reject('err');
})
  .then(
    (value) => {
      console.log('value1:', value);
    },
    (reason) => {
      throw reason;
    },
  )
  .then(
    (value) => {
      console.log('value2:', value);
    },
    (reason) => {
      throw reason;
    },
  )
  .then(
    (value) => {
      console.log('value3:', value);
    },
    (reason) => {
      throw reason;
    },
  )
  .catch((reason) => {
    console.log('reason:', reason);
  });

reason会逐级往下传递,直到最后的catch。

7. 如何中断promise链?

当使用promise.then链式调用时,若在中间过程中断,后面的then方法就不会再调用。

new Promise((resolve, reject) => {
  reject('err');
})
  .then((value) => {
    console.log('value1:', value);
  })
  .then(
    (value) => {
      console.log('value2:', value);
    },
    (reason) => {
      return new Promise(() => {});
    },
  )
  .then((value) => {
    console.log('value3:', value);
  })
  .catch((reason) => {
    console.log('reason:', reason);
  });

上面的代码最后没有任何输出。由于在第二个then方法的rejected回调中返回了一个pending状态的promise对象,因此后续的then方法也就不会再执行了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值