深入浅出JS红宝书 - Promise 使用小结(附关于取消Promise进程的思考)

89 阅读4分钟

Promise

  • resolve() & reject()

    let p1 = new Promise(
    (resolve, reject) => {
        resolve()
        reject() // 无效,状态不可撤销,走了其第一个就不会走第二个
      }
    )
    
    
  • Promise.resolve()

    // p1, p2 等价
    
    let p1 = new Promise( (resolve, reject) => resolve() )
    let p2 = Promise.resolve()
    
    // 只接受第一个参数
    
    console.log( Promise.resolve() ) 	// Promise {<resolved>: undefined}
    console.log( Promise.resolve(3) ) 	// Promise {<resolved>: 3}
    console.log( Promise.resolve(3, 4, 5) ) // Promise {<resolved>: 3}
    
  • Promise.reject()

    // Promise reject 的错误并没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理的。因此,try/catch块并不能捕获该错误。
    
    try {
      throw new Error('foo');
    } catch(e) {
      console.log(e); // Error: foo
    }
    
    try {
      Promise.reject(new Error('bar'));
    } catch(e) {
      console.log(e);
    }
    // Uncaught (in promise) Error: bar
    
  • Promise.prototype.then()

    // .then( param_1, param_2 )
    // param_1 { function } 执行 Promise 的 resolve
    // param_2 { function } 执行 Promise 的 reject
    
    let p_to_res = new Promise(
        (res, rej) => setTimeout(res, 3000)
    )
    let p_to_rej = new Promise(
        (res, rej) => setTimeout(rej, 3000)
    )
    
    let onRes = (id) => console.log(id, 'res')
    let onRej = (id) => console.log(id, 'rej')
    
    p_to_res.then( () => onRes('p_to_res'), () => onRej('p_to_res') )
    p_to_rej.then( () => onRes('p_to_rej'), () => onRej('p_to_rej') )
    
    // (3s后)
    // p_to_res res
    // p_to_rej rej
    
    // 非函数处理程序会被静默忽略,不推荐
    p_to_res.then( 'hello' ) 
    // 推荐
    p_to_rej.then( null, () => onRej('p_to_rej') )
    
    // .then()方法返回一个新的Promise实例:
    
    let p1 = new Promise(() => {});
    let p2 = p1.then(); 		
    
    console.log(p2) 				// Promise {<pending>}
    console.log(p1 === p2) 	// false
    
    // 参数传递
    
    let p = new Promise((resolve) => resolve('success!'))
    p.then((value) => console.log(value)) // success!
    
  • Promise.prototype.catch()

    // .catch( param )
    // param { function } 执行 Promise 的 reject
    // .catch( onRejected ) === .then(null, onRejected)
    
    let p = Promise.reject()
    let onRej = () => console.log('rej')
    
    // 下面两种方法等价
    p.then( null, onRej )
    p.catch( onRej )
    
    // 参数传递
    let p = new Promise((resolve, reject) => reject('fail!'))
    p.catch((value) => console.log(value)) // fail!
    
  • Promise.prototype.finally()

    // .finally( param )
    // param { function } 执行 Promise 的 resolve 或 reject
    
    let p_to_res = new Promise(
        (res, rej) => setTimeout(res, 3000)
    )
    let p_to_rej = new Promise(
        (res, rej) => setTimeout(rej, 3000)
    )
    
    let onFin = () => console.log('Finally')
    
    p_to_res.finally( onFin ) // Finally
    p_to_rej.finally( onFin ) // Finally
    

非重入(non-reentrancy) - about 执行顺序

// 即使promise状态立即落定,.then 仍在同步代码之后执行

let p = Promise.resolve()
p.then(() => console.log('run .then'))
console.log('after .then')

// after .then
// run .then
// 即使resolve()之后的console,也会先于 .then 执行

let syncFunc = null
let p = new Promise((resolve) => {
  syncFunc = () => {
    console.log('1: 调用 resolve')
    resolve()
    console.log('2: resolve 已执行')
  }
})

p.then(() => console.log('4: .then 已执行'))

syncFunc() // 此时 syncFunc 函数的定义已完成
console.log('3: syncFunc 已执行')

// 1: 调用 resolve
// 2: resolve 已执行
// 3: syncFunc 已执行
// 4: .then 已执行

副作用

// 正常情况下,在通过`throw()`关键字抛出错误时,JavaScript会停止执行抛出错误之后的任何指令

throw Error('error')
console.log('log see see') // 这行不会执行

// Uncaught Error Error: error
// 在Promise中,由于 non-reentrancy 特性,不会阻止同步指令执行

Promise.reject( Error('error') )
console.log('log see see')
// log see see
// UnhandledPromiseRejectionWarning: Error: error

Promise

// 由于`.then` `.catch` `.finally` 都会返回一个新的`Promise`对象

let func = new Promise((res, rej) => {
    console.log('1 done')
    res()
})

func
    .then(() => console.log('2 done'))
    .then(() => console.log('3 done'))
    .then(() => console.log('4 done'))

// 1 done
// 2 done
// 3 done
// 4 done
// 执行延时异步动作

let delay_1s_Func = (logText) => new Promise((res, rej) => {
    console.log(logText)
    setTimeout(res, 1000)
})

delay_1s_Func('p1 done')
    .then(() => delay_1s_Func('p2 done'))
    .then(() => delay_1s_Func('p3 done'))
    .then(() => delay_1s_Func('p4 done'))

// p1 done (1s 后)
// p2 done (2s 后)
// p3 done (3s 后)
// p4 done (4s 后)

Promise.all()

// 待数组中所有 Promise 都 resolve,执行下一步

// 常规用法
let p1 = Promise.all([
  Promise.resolve(),
  Promise.resolve(),
  Promise.resolve()
])

// 默认转换成 Promise.resolve()
let p2 = Promise.all([1, 2])
let p3 = promise.all([])

// 无效语法
let p4 = Promise.all()
//  UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
// .then 拿到所有 Promise 解决值的数组
// 顺序按原顺序

let p1 = new Promise((res) => {
  setTimeout(() => res('1s delay done'), 1000)
})
let p2 = new Promise((res) => {
  setTimeout(() => res('2s delay done'), 2000)
})
let p3 = new Promise((res) => {
  setTimeout(() => res('3s delay done'), 3000)
})

Promise.race([p1, p2, p3])
  .then( (res) => console.log(res) )

// ['1s delay done', '2s delay done', '3s delay done']
// 如果有一项 promise reject,则最终 reject
let p1 = new Promise((res) => {
  setTimeout(() => res('1s delay done'), 1000)
})
let p2 = new Promise((res, rej) => {
  setTimeout(() => rej('2s delay error'), 2000)
})
let p3 = new Promise((res) => {
  setTimeout(() => res('3s delay done'), 3000)
})

Promise.race([p1, p2, p3])
  .then( (res) => console.log(res) )
  .catch( (err) => console.log(err) )

// 2s delay error

Promise.race()

// 是一组集合中最先解决或拒绝的 Promise 的镜像

// 常规用法
let p1 = Promise.race([
  Promise.resolve(),
  Promise.resolve(),
  Promise.resolve()
])

// 默认转换成 Promise.resolve()
let p2 = Promise.race([1, 2])
let p3 = promise.race([])

// 无效语法
let p4 = Promise.race()
//  UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined
// 不会对解决或拒绝的期约区别对待。无论是解决还是拒绝,只要是第一个落定的Promise

let p1 = new Promise((res) => {
  setTimeout(() => res('1s delay done'), 1000)
})
let p2 = new Promise((res, rej) => {
  setTimeout(() => rej('2s delay error'), 2000)
})
let p3 = new Promise((res) => {
  setTimeout(() => res('3s delay done'), 3000)
})

Promise.race([p1, p2, p3])
  .then( (res) => console.log(res) )
  .catch( (err) => console.log(err) )
// 1s delay done

Promise.race([p2, p3])
  .then( (res) => console.log(res) )
  .catch( (err) => console.log(err) )
// 2s delay error

Promise 扩展

ECMAScript规范却未涉及的两个特性:取消进度追踪

Promise取消

第三方库:Bluebird

现有实现基础可以提供一种临时封装 - 取消令牌 CancelToken

class CancelToken {
  constructor(cancelFn) {
    this.promise = new Promise((resolve, reject) => {
      cancelFn(resolve);
    });
  }
}
// 用法

<button id="start">Start</button>
<button id="cancel">Cancel</button>

<script>
class CancelToken {
  constructor(cancelFn) {
    this.promise = new Promise((resolve, reject) => {
      cancelFn(() => {
        setTimeout(console.log, 0, "delay cancelled");
        resolve();
      });
    });
  }
}

const startButton = document.querySelector('#start');
const cancelButton = document.querySelector('#cancel');

function cancellableDelayedResolve(delay) {
  setTimeout(console.log, 0, "set delay");

  return new Promise((resolve, reject) => {
    const id = setTimeout((() => {
      setTimeout(console.log, 0, "delayed resolve");
      resolve();
    }), delay);

    const cancelToken = new CancelToken((cancelCallback) =>
      cancelButton.addEventListener("click", cancelCallback));

    cancelToken.promise.then(() => clearTimeout(id));
  });
}

startButton.addEventListener("click", () => cancellableDelayedResolve(1000));
</script>
// vue3 版本

<script setup>
class CancelToken {
  constructor(cancelFn) {
    this.promise = new Promise((resolve) => {
      cancelFn(() => resolve())
    })
  }
}
const start = () => delayDoSomething(1000)
const end = () => _end()
let _end = () => {}
const delayDoSomething = ( delay ) => {
  console.log('set delay')
  return new Promise((resolve, reject) => {
    const id = setTimeout(() => {
      console.log('delayed resolve')
      resolve()
    }, delay);
    const cancleToken = new CancelToken( (cancelCallback) => _end = cancelCallback )
    cancleToken.promise.then(() => clearTimeout(id))
  })
}
</script>

<template>
  <div>
    <button @click="start">start</button>
    <button @click="end">end</button>
  </div>
</template>