《promises-book》学习笔记

195 阅读3分钟

本文是在阅读 《promises-book》过程中, 记录了个人觉得在 Promise 使用中,容易被忽视但又重要的细节。

每一个知识点,都通过编写对应的测试代码,来验证和加深印象

读者朋友可以先思考标题或想下代码会输出什么,再对比控制台输出的结果,理解和记忆更深刻^_^

其实是 18年7月记录的个人笔记,现在重新整理分享出来哈

您的点赞和评论,是我继续写作的动力~~~

一、构造函数中执行顺序是怎样的?

测试代码:

function testConstructor() {
  console.log('1. Init');
  const promise = new Promise((resolve) => {
    resolve('4. Promise Then');
    console.log('2. Call in Promise constructor paremeter');
  });
  promise.then((msg) => {
    console.log(msg);
  });
  console.log('3. End');  
}
testConstructor();

输出结果:

"1. Init"
"2. Call in Promise constructor paremeter"
"3. End"
"4. Promise Then"

解释说明:

Promise 构造函数传进的函数会立即执行, 相当于如下

console.log('1. Init');
(resolve) => {
    resolve('4. Promise Then');
    console.log('2. Call in Promise constructor paremeter');
})();
console.log('3. End');  

先打印 1. Init 后紧接着 2. Call in Promise constructor paremeter3. End

浏览器执行完宏任务后,会执行微任务,执行 promise.then 里的回调函数,console.log(msg); , 即 4. Promise Then

二、取消一个 promise

取消一个 promise 的方式有什么呢?

整理了两种方式,读者朋友可以继续补充、赐教哈

其实并没有真正地停止原有 promise 的执行,而是通过让 resolve 提前执行,或 封装原有Promise(Promise.race), 实现了取消的效果

方式一 cancelToken

测试代码:

const source = {
    cancelToken: null,
}
function cacelPromise1(source) {
  const p1 = new Promise(function(resolve, reject) {    
    setTimeout(() => {
      resolve('Fullfill after 3s');
    }, 3000);
    console.log('1. refer source.cancelToken to resolve');
    source.cancelToken = resolve;
  });
  // mResolve('Fullfill at once');
  p1.then((msg) => {
    console.log(msg);
  })
}
cacelPromise1(source);

console.log('2. source.cancelToken...');
source.cancelToken('3. Promise resolve before Fullfill after 3s');

输出结果:

"1. refer source.cancelToken to resolve"
"2. source.cancelToken..."
"3. Promise resolve before Fullfill after 3s"

解释说明:

通过让 source.cancelToken 指向 resolve, 在等待 3s 的 setTimeout 回调执行以前,执行了 source.cancelToken('3. Promise resolve before Fullfill after 3s');, 即 resolve('3. Promise resolve before Fullfill after 3s')

这样 setTimeout 3s 到了 再进行 resovle, 也不会影响 p1.thenmsg 的数据

注意:传入 cacelPromise1 的参数使用 source.cancelToken,而没有直接使用 cancelToken ,是为了利用函数调用时,参数的传递是浅复制的特性。

提示:多次调用 resolve ,也不会影响 p1.thenmsg 的数据,可以参考下文

方式二 Promise.race

测试代码

// how to cacel a promise
function cacelPromise2() {
  const p1 = new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve('Fullfill after 3s');
    }, 3000);
  });
  const p2 = new Promise((resolve) => {
    setTimeout(() => {
      resolve('Ahead');
    }, 100);
  })
  Promise.race([p1, p2]).then((value) => {
    console.log(value);
  })
}

cacelPromise2();

输出结果

"Ahead"

三、多次调用 resolve 和 then

测试代码:

function testThenRevoke() {
  console.log('1. Init');
  const promise = new Promise((resolve) => {
    resolve('Resolve First');
    resolve('Resove Second');
    console.log('2. Call in Promise constructor');
  });
  promise.then((value) => {
    console.log('Then First: ', value);
  });
  promise.then((value) => {
    console.log('Then Second: ', value);
  });
  console.log('3. End');  
};
testThenRevoke();

输出结果:

"1. Init"
"2. Call in Promise constructor"
"3. End"
"Then First: Resolve First"
"Then Second: Resolve First"

解释说明:

多次调用 resolve, 只接收第一个 resolve 的调用

四、then 方法的第二个参数

测试代码:

function testReject() {
  console.log('1. Init');
  const promise = new Promise((resolve) => {
    undefined.key;
    console.log('Call in Promise constructor');
  });
  promise
      .then(
        function onFulfilled(value) {
          console.log('Then onFulfilled: ', msg);
        },
        function onRejected(reason) {
          console.log('3. Then onRejected: ' + reason);
          // throw new Error(reason);
        },
      )
      .catch((error) => {
        // 传进 Promise catch 的回调将不会执行
        // 因为 promise onRejected 默认会 throw new Error(reason);
        console.log('Catch: ' + error);
      });
  console.log('2. End');  
};
testReject();

输出结果:

"1. Init"
"2. End"
"3. Then onRejected: TypeError: Cannot read property 'key' of undefined"

解释说明:

undefined.key 触发异常后,后面的 console.log('Call in Promise constructor') 就不会再执行。

如果 promise.then 没传入第二个参数 onRejected,就会进入 promise.catch,因为 promise 默认的 onRejected 默认会 throw new Error(reason)

测试代码中传进 onRejected, 又没有 throw new Error(reason), 后面的 catch 就不会执行了。

function onRejected(reason) {
    console.log('3. Then onRejected: ' + reason);
    // throw new Error(reason);
}

五、Promise.resolve

测试代码:

function testPromiseResolve() {
  const p1 = new Promise(function(resolve) {
    console.log('resolve begin');
    setTimeout(() => {
      resolve(42);
    }, 3000);
    console.log('resolve end');
  });
  const p2 = Promise.resolve(42);
  const p3 = Promise.resolve(p1);
  console.log('p1 === p2: ' + (p1 === p2));
  console.log('p1 === p3: ' + (p1 === p3));
}

testPromiseResolve();

输入结果:

"resolve begin"
"resolve end"
"p1 === p2: false"
"p1 === p3: true"