JavaScript – Promise

前言

我学 Promise 的时候, 那时还没有 es6. 曾经还自己实现过. 但时隔多年, 现在 es6 的 promise 已经很完善了.

这篇作为一个简单的复习. (毕竟我已经 1 年多没有写 JS 了...)

以前写过相关的文章:

Javascript Promise 学习(上)

Javascript Promise 学习 (中)

$q 就是angular 的promise

angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )

 

参考

阮一峰 – Promise 对象

 

Promise 解决的问题

什么是异步和回调 callback

JS 是单线程, 无法并发处理事情, 但是它可以异步. 比如发 http request, request 通过网卡发出去后, CPU 就去做别的事情, 而不是傻傻等网卡回复.

当 response 回来以后, 网卡通知 CPU, 这时再把任务接回来. 这个动作就叫 callback. 就是你别等我, 我好了会通知你.

callback 的写法长这样

const callback = () => {
  console.log('timeout');
};
setTimeout(callback, 2000);

setTimeout 是一个异步函数, 调用后, 游览器会用另一个线程去计算时间, 主线程继续处理其它代码. 时间到, 主线程会被通知, 然后运行后续 (callback) 的代码.

大概是这个概念. 其它的异步函数包括 Ajax, FileReader 等等 (通常涉及到磁盘 IO, 网路请求都会是异步的. 因为做这些事情的时候不需要 CPU).

回调地狱

callback 的写法一旦嵌套就会变成很丑, unreadable.

比如, 我想写一个

delay 3 秒,

运行 console 'a'

再 delay 2 秒

运行 console 'b'

再 delay 1 秒

运行 console 'c'

写出来长这样:

setTimeout(() => {
  console.log('a');
  setTimeout(() => {
    console.log('b');
    setTimeout(() => {
      console.log('c');
    }, 1000);
  }, 2000);
}, 3000);

丑不丑? Promise 就是用来解决丑这个问题的. 它可以把嵌套的回调 "打平" flat

 

Promise 基本用法

Promise 的核心是封装了异步函数的调用, 和 callback 的写法, 记住这个点.

promise 使用是这样的

const promise = new Promise((resolve, _reject) => {
  setTimeout(() => {
    resolve('return value 123');
  }, 3000);
});
promise.then((returnValue) => {
  console.log('returnValue', returnValue); // return value 123
});

分 2 个阶段看待

初始化 Promise

Promise 是一个 class. 实例化它会等到 promise 对象.

实例化时, 需要传入一个函数. 函数里面封装了要执行的异步代码.

比如上面的 setTimeout. 或者是 Ajax, FileReader 等等都行.

resolve 是一个 callback 代理, 当异步完成以后. 我们调用 resolve 告知 Promise 异步完成了. 并且返回异步函数的返回值 (比如 Ajax 后的 response data)

注册 callback

注册 callback 是通过 promise 对象来实现的. 调用 .then 函数把 callback 传进去

callback 会在 resolve 的时候被执行, 并且获得异步函数的返回值.

注: 有没有返回值都是 ok 的.

意义何在? 

像这样把异步函数和 callback wrap 起来, 意义何在呢? 如果只是 1 个 callback 那么没有什么太大的意义.

记得, Promise 要解决的是嵌套的 callback (回调地狱)

 

.then Combo

我们把上面的 setTimeout 用 Promise 封装一下

function delayAsync(delayTime: number): Promise<void> {
  const promise = new Promise<void>((resolve, _reject) => {
    setTimeout(() => {
      resolve();
    }, delayTime);
  });
  return promise;
}

delayAsync(3000).then(() => {
  console.log('a');
});

相等于

setTimeout(() => {
  console.log('a');
}, 3000);

return promise + .then combo

如果要再嵌套一个 delay, 你可能会认为是这样写

delayAsync(3000).then(() => {
  console.log('a');
  delayAsync(2000).then(() => {
    console.log('b');
  });
});

虽然这个也可以跑, 但是正确的用法不是这样. 而是这样

delayAsync(3000)
  .then(() => {
    console.log('a');
    return delayAsync(2000);
  })
  .then(() => {
    console.log('b');
  });

在第一个 then 里, 我们返回了另一个 promise 对象.

然后再第一个 then 之后 combo 了另一个 then.

这样的写法就成功的把嵌套的回调 "打平" 了.

Promise 内部实现原理

其实没有必要懂底层逻辑, 会用就可以了. 简单了解一下到时可以啦.

Promise 对象的 then 负责注册 callback. 同时它返回另一个 promise. 你可以把它理解为一个 child promise (连续几个 .then 就变成了一个 promise chain)

callback 除了可以返回普通的 value 也可以返回一个 promise 对象.

当返回 promise 对象, Promise 就会等待这个 promise 对象 resolve 才执行 callback.

Promise 内部就是维护着 promise chain 和所以 callback 的执行顺序. 这样就做到了 "打平" 的写法了.

感悟

Promise 在 es6 之前就有了, 在 JS 的语法基础上, 通过封装实现另一种调用方式, 让代码更好写, 更好读.

jQuery 也是有这种 feel. 还有 Fluent Builder 模式 也是这样. 都是很聪明的实现.

 

Promise 的执行顺序

console.log('1');
const promise = new Promise<void>((resolve) => {
  console.log('2');
  resolve();
  console.log('3');
});
promise.then(() => {
  console.log('5');
});
console.log('4');

new Promise 传入的函数会马上被执行 (里面通常会调用异步函数, 但并没有强制, 你也可以直接调用 resolve 返回的)

上面我刻意搞了一个同步执行的情况, resolve 虽然马上被执行了, 但是 callback 并没有马上被执行.

一直等到 console.log(4) 完了以后 callback 才被执行.

也就是说任何 promise 的 callback 都会被押后执行, 即使 resolve 没有被异步调用. 这个是唯一需要特别注意的.

after resolve 依然执行代码 ?

best practice 的话, resolve 之后就不应该执行代码了.

刻意习惯性的在 resolve 前加上 return, 确保后续没有执行代码. (不然挺乱的)

const promise = new Promise<void>((resolve) => {
  console.log('2');
  return resolve();
});

 

Reject and Catch

上面提到的例子都是 succeed 的情况. Promise 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章