目录

Promise小专题

Promise相关知识点由浅入深包含以下这些。掌握这些,能让你基本功更扎实,写代码更得心应手,面试更无往不利。

  1. 熟练使用Promise封装代码,如封装axios,Promise级联使用,掌握async、await的用法
  2. 掌握Promise静态方法的使用场景、用法,Promise.all、Promise.allSettled、Promise.race、Promise.any、Promise.resolve、Promise.reject
  3. 手写Promise.all、Promise.race、Promise.allSettled、手写Promise.any、Promise.last、Promise.queue
  4. 并发请求限制数量封装
  5. 实现Promisify
  6. Promise 微任务输出、async/await 微任务输出
  7. 理解Promise A+规范,手写Promise

一、Promise封装ajax

function ajax(url = '', method = 'GET', data = {}) {
  return new Promise((resolve, reject) => {
    let options = { method }
    if(method === 'GET') {
      url += '?' + Object.entries(data).map(arr => arr[0] + '=' + arr[1]).join('&')
    } else if(method === 'POST') {
      options.body = JSON.stringify(data)
      options.headers = { 'Content-Type': 'application/json' }
    }
    fetch(url, options).then(res => res.json())
    .then(data => resolve(data))
    .catch(e => reject(e))
  })
}
//使用范例
ajax('http://api2.jirengu.com/getWeather.php', 'GET', {city: '杭州'})
 .then(data => console.log(data))
 .catch(e => console.log(e))
 
ajax('https://note-server.hunger-valley.com/auth/login', 'POST', {username: 'jirengu', password: '123456'})
 .then(data => console.log(data))
 .catch(e => console.log(e))

二、理解Promise静态方法

Promise.all

Promise.all() 方法接收一个数组做为输入严格来说是iterable类型,Array,Map,Set都属于iterable类型),数组的每一项是Promise实例(如果不是会创建一个resolve的Promise实例),运行结果返回一个Promise实例。 当数组里promise全部 resolve时再resolve,结果是所有promise resolve的结果构成的一个数组。 如果中途任何一个promise 执行reject,则Promise.all得到的Promise实例立即reject,

let p1 = new Promise(resolve => setTimeout(resolve, 2000, 1))
let p2 = new Promise(resolve => setTimeout(resolve, 3000, 2))
let p3 = new Promise((resolve, reject) => {
  let v = Math.random()
  if(v > 0.5) setTimeout(resolve, 2000, v) 
  else setTimeout(reject, 2000, v)
})
 
Promise.all([p1, p2, p3])
  .then(value => console.log(value)) //[1, 2, 0.7685334]
  .catch(reason => console.error(reason))  //或者 0.2342321
console.log(p1, p2, p3)
//如果p1 p2 p3都resolve,则当前promise实例 resolve,va是p1 p2 p3 resolve的结果构成的数组
//如果p1 p2 p3 任一个reject,则当前promise实例立即reject,reason是p1 p2 p3中最先reject的值

Promise.allSettled

该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

let p1 = new Promise((resolve, reject) => setTimeout(resolve, 2000, 1))
let p2 = new Promise((resolve, reject) => setTimeout(resolve, 3000, 2))
let p3 = new Promise((resolve, reject) => setTimeout(reject, 2000, 3))
 
Promise.allSettled([p1, p2, p3])
  .then(value => console.log(value))
/* 输出结果
[
 {"status":"fulfilled","value":1},
 {"status":"fulfilled","value":2},
 {"status":"rejected","reason":3}
]
*/

总结一下,Promise.all 会拿到所有promise对象resolve的结果,但如果中间任意一个promise提前reject则立即退出进入失败逻辑;Promise.allSettled会等所有promise全部有结果(不管成功还是失败)后拿到所有结果构成的数组。

Promise.race

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个promise resolve或reject,返回的 promise就会resolve或reject。

let p1 = new Promise((resolve, reject) => setTimeout(resolve, Math.random()*1000, 1))
let p2 = new Promise((resolve, reject) => setTimeout(resolve, Math.random()*1000, 2))
let p3 = new Promise((resolve, reject) => setTimeout(reject, Math.random()*1000, 3))
 
 
Promise.race([p1, p2, p3])
  .then(value => console.log(value))  //p1 p2 p3谁最先resolve,就进入这里
  .catch(reason => console.error(reason)) ////p1 p2 p3谁最先reject,就进入这里

三、手写Promise.all、Promise.race、Promise.allSettled、Promise.any、Promise.last

手写Promise.all

//参数:一个迭代器对象,比如 String, Array, TypedArray, Map, and Set
//如果传递了一个空迭代器,返回的Promise对象会同步的resolve,resolve的值是空对象
//如果传递了一个非空迭代器,并且所有的promise都是fulfill状态的,或者不是promise对象,那么返回的Promise对象会异步的resolve
//如果任一个传递的promise对象被reject,Promise.all会异步的reject,reject的值是那个传递的promise对象reject的值
 
Promise.all = function(iterable) {
  return new Promise((resolve, reject) => {
    let promises = [...iterable].map(p => (p instanceof Promise) ? p : Promise.resolve(p))
    if(promises.length === 0) return resolve([])
    let values = []
    let count = 0
    for(let i=0; i < promises.length; i++) {
      promises[i].then(v => {
        values[i] = v
        count++
        if(count === promises.length) {
          resolve(values)
        }
      }, reject)
    } 
  })
}

手写Promise.race

//参数,一个iterable对象。比如 String, Array, TypedArray, Map, and Set 
//race 函数返回一个 Promise,它将与iterable对象里最先resolve或者reject的 promise 相同的完成方式被处理。
//如果传的迭代是空的,则返回的 promise 一直处于 pending。
//如果迭代包含一个或多个非Promise的值,或者包含已经resolve或者reject的Promise对象,则 Promise.race 把第一个这种值拿作为被处理的对象。
Promise.race = function(iterable) {
  return new Promise((resolve, reject) => {
    let promises = [...iterable].map(p => (p instanceof Promise) ? p : Promise.resolve(p))
    for(let i=0; i<promises.length; i++) {
      promises[i].then(resolve, reject)
    }
  })
}
 
 
//测试
//eg1: 迭代器包含非Promise值和已resolve的Promise对象,以第一个为准
Promise.race([3, Promise.resolve(4), 5])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))
 
//eg2: 迭代为空,返回pending状态的promise
console.log(Promise.race(''))
 
let p1 = new Promise(r => setTimeout(r, 100, 1))
let p2 = new Promise(r => setTimeout(r, 200, 2))
let p3 = new Promise((r, j) => setTimeout(j, 50, 3))
 
//eg3: 迭代器里包含已经resolve的promise和pending状态的promise,以已resolve的promise对象为准
Promise.race([Promise.reject(4), p1, p2])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))
 
//eg3: 迭代器里包含pending状态的promise,以最先resolve或者reject的额的为准
Promise.race([p1, p2])
  .then(v => console.log(v))
  .catch(reason=> console.log(reason))
 
Promise.race([p1, p3])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))

手写Promise.allSettled

//参数:一个iterable对象。比如 String, Array, TypedArray, Map, and Set 
//返回一个Promise对象,会异步resolve,结果是一个数组,里面每一项是个对象,包含 status 和 value 或者reason两个属性。每个对象是iterable对象每一个 resolve或者reject的结果构成的对象
//如果iterable对象为空,则返回一个已经被resolve的Promise对象,resolve内容为空数组
Promise.allSettled = function(iterable) {
  return new Promise((resolve, reject) => {
    let promises = [...iterable].map(p => (p instanceof Promise) ? p : Promise.resolve(p))
    if(promises.length === 0) return resolve([])
    let values = []
    let count = 0
    for(let i=0; i<promises.length; i++) {
      promises[i].then(v => {
        values[i] = { status: 'fulfilled', value: v }
      }, reason => {
        values[i] = { status: 'rejected', reason: reason }
      }).finally(() => {
        count++
        if(count === promises.length) {
          resolve(values)
        }
      })
    }
  })
}
 
 
//测试
Promise.allSettled([3, Promise.resolve(4), 5])
  .then(v => console.log(v))
 
console.log(Promise.allSettled(''))
 
let p1 = new Promise(r => setTimeout(r, 100, 1))
let p2 = new Promise(r => setTimeout(r, 200, 2))
 
Promise.allSettled([Promise.reject(4), p1, p2])
  .then(v => console.log(v))

手写Promise.any

//参数:一个iterable对象。比如 String, Array, TypedArray, Map, and Set 
//返回一个Promise对象,
//如果iterable为空,则返回一个已经rejected的Promise
//如果iterablee传递的不包含promise对象,则返回一个异步resolved的Promise
//其他情况,返回一个pending状态的Promise。当任何一个参数promise对象resolve时,这个Promise对象异步resolve。当所有参数promise对象都reject时,这个Promise对象异步reject
Promise.any = function(iterable) {
  return new Promise((resolve, reject) => {
    let promises = [...iterable].map(p => (p instanceof Promise) ? p : Promise.resolve(p))
    if(promises.length === 0) return reject('AggregateError: All promises were rejected')
    let rejectCount = 0
    for(let i=0; i<promises.length; i++) {
      promises[i].then(resolve, reason => {
        rejectCount++
        if(rejectCount === promises.length) {
          reject('AggregateError: All promises were rejected')
        }
      })
    }
  })
}
 
 
 
//测试
Promise.any([3, Promise.reject(4), 5])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))
 
console.log(Promise.any2('').catch(e=>console.error(e)))
 
let p1 = new Promise(r => setTimeout(r, 100, 1))
let p2 = new Promise(r => setTimeout(r, 200, 2))
 
 
Promise.any([Promise.reject(4), p1, p2])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))
 
Promise.any([p1, p2])
  .then(v => console.log(v))
  .catch(reason=> console.log(reason))
 
let p3 = new Promise((r, j) => setTimeout(j, 50, 3))
Promise.any([p3])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))

手写Promise.last

//Promise.first 等同于Promise.any
//反问: Promise.last怎么实现?
Promise.last = function(iterable) {
  return new Promise((resolve, reject) => {
    let promises = [...iterable].map(p => (p instanceof Promise) ? p : Promise.resolve(p))
    if(promises.length === 0) return reject('AggregateError: All promises were rejected')
    let resolveCount = 0
    let rejectCount = 0
    let lastValue = null
    for(let i=0; i<promises.length; i++) {
      promises[i].then(v => {
        resolveCount++
        lastValue = v
      }, reason => {
        rejectCount++
      }).finally(() => {
        if(rejectCount === promises.length) {
          reject('AggregateError: All promises were rejected')
        } else if(resolveCount + rejectCount === promises.length) {
          resolve(lastValue)
        }
      })
    }
  })
}
 
 
 
let p1 = new Promise(r => setTimeout(r, 100, 1))
let p2 = new Promise(r => setTimeout(r, 200, 2))
let p3 = new Promise(r => setTimeout(r, 300, 3))
 
 
Promise.last([Promise.reject(4), p1, p2, p3])
  .then(v => console.log(v))
  .catch(reason=> console.error(reason))

手写Promise.queue

Promise.queue = function(arr, initValue) {
  return new Promise((resolve, reject) => {
    let sequence = Promise.resolve(initValue)
    arr.forEach(item => {
      sequence = sequence.then(item)
    })
    sequence.then(resolve, reject)
  })
}
 
 
 
const f1 = function(v) {
  console.log('f1', v)
  return new Promise((resolve, reject) => setTimeout(resolve, 1000, v+1)) 
}
 
const f2 = function(v) {
  console.log('f2', v)
  return new Promise((resolve, reject) => setTimeout(resolve, 1000, v+1)) 
}
 
 
Promise.queue([f1, f2], 10).then(v => console.log(v))

四、并发请求限制数量封装

function asyncPool(fn, arr, limit = 10) {
  let args = [...arr]   //不修改原参数数组
  let results = []      //存放最终结果
  let runningCount = 0  //正在运行的数量
  let resultIndex = 0   //结果的下标,用于控制结果的顺序
  let resultCount = 0   //结果的数量
 
  return new Promise((resolve) => {
    function run() {
      while(runningCount < limit && args.length > 0) {
        runningCount++
        ((i)=> {        //闭包用于保存结果下标,便于在resolve时把结果放到合适的位置
          let v = args.shift()
          console.log('正在运行' + runningCount)
          fn(v).then(val => {
            results[i] = val
          }, () => {
            throw new Error(`An error occurred: ${v}`)
          }).finally(() => {
            runningCount--
            resultCount++
            if(resultCount === arr.length) {  //这里之所以用resultCount做判断,而不用results的长度和args的长度,是因为这两个都不准确
              resolve(results)
            } else {
              run()
            }
          })          
        })(resultIndex++)
      }
    }
    run()
  })
}
 
 
// 测试
function getWeather(city) {
  console.log(`开始获取${city}的天气`)
  return fetch(`https://api2.jirengu.com/getWeather.php?city=${city}`).then(res=> res.json())
}
 
let citys = ['北京', '上海', '杭州', '成都', '武汉', '天津', '深圳', '广州', '合肥', '郑州']
asyncPool(getWeather, citys, 2).then(results => console.log(results))

五、实现Promisify

function promisify(fn, context = null) {
  return function(...args) {
    return new Promise((resolve, reject) => {
      fn.bind(context)(...args, function(err, val) {
        if(err !== null) reject(err)
        else resolve(val)
      })
    })
  }
}
 
 
//测试
function add(a, b, callback) {
  setTimeout(() => {
    if(a + b <= 100) {
      callback(null, a + b)
    } else {
      callback('大于 100')
    }
  }, 1000)
}
 
add(3, 4, function(err, val) {
  if(err) {
    console.error(err)
    return
  }
  console.log(val)
})
 
let add2 = promisify(add)
add2(100, 50)
  .then(v => console.log(v))
  .catch(err => console.error(err))

六、Promise 微任务输出、async/await 微任务输出

https://zhuanlan.zhihu.com/p/449183802https://zhuanlan.zhihu.com/p/450906325

未完待续

饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com