ES6(十一)—— Promise(更优的异步编程解决方案)

目录

  • 说到Promise就不得不说道说道这 —— 回调地狱
  • Promise —— 解决回调地狱html

    • Promise语法规范
    • Promise的状态
    • Promise基本用法
    • Promise初体验
    • Promise的本质
    • Promise链式调用html5

      • 常见误区
      • 链式调用的理解
  • Promise.prototype.then()
  • Promise异常处理node

    • then中回调的onRejected方法
    • Promise.prototype.catch()(推荐)ajax

      • .catch形式和前面then里面的第二个参数的形式,二者异常捕获的区别:
    • 全局对象上的unhandledrejection事件
  • Promise静态方法编程

    • 类型转换 —— Promise.resolve()json

      • 使用场景
    • Promise.reject()
    • 数据聚合 —— Promise.all()
    • 竞争 —— Promise.race()
  • Promise执行时序 —— 宏任务 vs 微任务
  • 深度剖析:手写一个Promise源码
  • ES6-ES10学习版图

说到Promise就不得不说道说道这 —— 回调地狱

a => b => c => dsegmentfault

回调层数越深,那么回调的维护成本越高api

//异步加载函数
function loadScript (src, callback) {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => {
        callback()
    }
    document.head.append(script)
}

function test () {
    console.log('test')
}
loadScript('./1.js', test)

// 1
// test

若是有三个这样的方式回调数组

function loadScript (src, callback) {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => {
        callback(src)
    }
    document.head.append(script)
}

function test (name) {
    console.log(name)
}
loadScript('./1.js', function (script) {
    console.log(script)
    loadScript('./2.js', function (script) {
        console.log(script)
        loadScript('./3.js', function (script) {
            console.log(script)
            //...
        })
    })
})

// 1
// ./1.js
// 2
// ./2.js
// 3
// ./3.js

Promise —— 解决回调地狱

虽然回调函数是全部异步编程方案的根基。可是若是咱们直接使用传统回调方式去完成复杂的异步流程,就会没法避免大量的回调函数嵌套。致使回调地狱的问题。promise

为了不这个问题。CommonJS社区提出了Promise的规范,ES6中称为语言规范。

Promise是一个对象,用来表述一个异步任务执行以后是成功仍是失败。

Promise语法规范

new Promise( function(resolve, reject) {…} );

  • new Promise(fn) 返回一个Promise 对象
  • fn中指定异步等处理

    • 处理结果正常的话,调用resolve(处理结果值)
    • 处理结果错误的话,调用reject(Error对象)

Promise的状态

Promise 内部是有状态的 (pending、fulfilled、rejected)Promise 对象根据状态来肯定执行哪一个方法。Promise 在实例化的时候状态是默认 pending 的,

  • 当异步操做是完成的,状态会被修改成 fulfilled
  • 若是异步操做遇到异常,状态会被修改成 rejected

不管修改成哪一种状态,以后都是不可改变的。

Promise基本用法

返回resolve

const promise = new Promise((resolve, reject) => {
  resolve(100)
})

promise.then((value) => {
  console.log('resolved', value) // resolve 100
},(error) => {
  console.log('rejected', error)
})

返回reject

const promise = new Promise((resolve, reject) => {
  reject(new Error('promise rejected'))
})

promise.then((value) => {
  console.log('resolved', value)
},(error) => {
  console.log('rejected', error)
  // rejected Error: promise rejected
  //  at E:\professer\lagou\Promise\promise-example.js:4:10
  //  at new Promise (<anonymous>)
})

即使promise中没有任何的异步操做,then方法的回调函数仍然会进入到事件队列中排队。

Promise初体验

使用Promise去封装一个ajax的案例

function ajax (url) {
  return new Promise((resolve, rejects) => {
    // 建立一个XMLHttpRequest对象去发送一个请求
    const xhr = new XMLHttpRequest()
    // 先设置一下xhr对象的请求方式是GET,请求的地址就是参数传递的url
    xhr.open('GET', url)
    // 设置返回的类型是json,是HTML5的新特性
    // 咱们在请求以后拿到的是json对象,而不是字符串
    xhr.responseType = 'json'
    // html5中提供的新事件,请求完成以后(readyState为4)才会执行
    xhr.onload = () => {
      if(this.status === 200) {
        // 请求成功将请求结果返回
        resolve(this.response)
      } else {
        // 请求失败,建立一个错误对象,返回错误文本
        rejects(new Error(this.statusText))
      }
    }
    // 开始执行异步请求
    xhr.send()
  })
}

ajax('/api/user.json').then((res) => {
  console.log(res)
}, (error) => {
  console.log(error)
})

Promise的本质

本质上也是使用回调函数的方式去定义异步任务结束后所须要执行的任务。这里的回调函数是经过then方法传递过去的

Promise链式调用

常见误区
  • 嵌套使用的方式是使用Promise最多见的误区。要使用promise的链式调用的方法尽量保证异步任务的扁平化。
链式调用的理解
  • promise对象then方法,返回了全新的promise对象。能够再继续调用then方法,若是return的不是promise对象,而是一个值,那么这个值会做为resolve的值传递,若是没有值,默认是undefined
  • 后面的then方法就是在为上一个then返回的Promise注册回调
  • 前面then方法中回调函数的返回值会做为后面then方法回调的参数
  • 若是回调中返回的是Promise,那后面then方法的回调会等待它的结束

Promise.prototype.then()

promise对象就能够调用.then(),是promise原型对象上的方法

promise.then(onFulfilled,onRejected);

onFulfilled 参数对应 resolve,处理结果值,必选

onRejected 参数对应 reject,Error对象,可选

Promise 对象会在变为 resolve 或者 reject 的时候分别调用相应注册的回调函数。

  • handler 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 方法。
  • 定义的 handler 中产生异常的时候,这个值则会传递给 Promise 对象的 onRejected 方法。

这两个参数都是两个函数类型,若是这两个参数是非函数或者被遗漏,就忽略掉这两个参数了,返回一个空的promise对象。

// 普通的写法会致使有不稳定输出
function loadScript (src) {
    //resolve, reject是能够改变Promise状态的,Promise的状态是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(loadScript('./2.js'))
    .then(loadScript('./3.js'))
    
//不稳定输出    
// 1
// 2
// 3
----------------------------------------------------------------------------
// 若是把加载2和3的放在1的then方法中
function loadScript (src) {
    //resolve, reject是能够改变Promise状态的,Promise的状态是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}

loadScript('./1.js')
    .then(() => {
        loadScript('./2.js')
    }, (err) => {
        console.log(err)
    }).then( () => {
        loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })
    
// 稳定输出
// 1
// 不稳定输出
// 2
// 3
// ----------------------------------------------
//可是若是中间有错误的时候,下面的3仍是会执行。
loadScript('./1.js')
    .then(() => {
        loadScript('./4.js')
    }, (err) => {
        console.log(err)
    }).then( () => {
        loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })

// 1
// 报错
// 3
// 不符合题意,若是是报错以后,3不该该执行
// -------------------------------------------------------
loadScript('./1.js')
    .then(() => {
        return loadScript('./2.js')
    }, (err) => {
        console.log(err)
    }).then(() => {
        return loadScript('./3.js')
    }, (err) => {
        console.log(err)
    })
// 不加返回值,依旧是一个空的promise对象,没法用resolve, reject影响下一步.then()的执行
// 添加返回值以后就能够稳定输出
// 1
// 2
// 3

Promise异常处理

异常处理有如下几种方法:

then中回调的onRejected方法

Promise.prototype.catch()(推荐)

catch是promise原型链上的方法,用来捕获reject抛出的一场,进行统一的错误处理,使用.catch方法更为常见,由于更加符合链式调用

p.catch(onRejected);
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  }).catch(function onRejected(error) {
    console.log('onRejected', error)
  })
  
// 至关于
ajax('/api/user.json')
  .then(function onFulfilled(res) {
    console.log('onFulfilled', res)
  })
  .then(undefined, function onRejected(error) {
    console.log('onRejected', error)
  })
.catch形式和前面then里面的第二个参数的形式,二者异常捕获的区别:
  • .catch()是对上一个.then()返回的promise进行处理,不过第一个promise的报错也顺延到了catch
  • then的第二个参数形式,只能捕获第一个promise的报错,若是当前thenresolve函数处理中有报错是捕获不到的。

因此.catch是给整个promise链条注册的一个失败回调。推荐使用!!!!

function loadScript (src) {
    //resolve, reject是能够改变Promise状态的,Promise的状态是不可逆的
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        script.src = src
        script.onload = () => resolve(src) //fulfilled,result
        script.onerror = (err) => reject(err) //rejected,error
        document.head.append(script)
    })
}


loadScript('./1.js')
    .then(() => {
        return loadScript('./2.js')
    }).then(() => {
        return loadScript('./3.js')
    })
    .catch(err => {
        console.log(err)
    })
// throw new Error 不要用这个方法,要用catch和reject,去改变promise的状态的方式

全局对象上的unhandledrejection事件

还能够在全局对象上注册一个unhandledrejection事件,处理那些代码中没有被手动捕获的promise异常,固然并不推荐使用

更合理的是:在代码中明确捕获每个可能的异常,而不是丢给全局处理

// 浏览器
window.addEventListener('unhandledrejection', event => {
  const { reason, promise } = event
  console.log(reason, promise)

  //reason => Promise 失败缘由,通常是一个错误对象
  //promise => 出现异常的Promise对象

  event.preventDefault()
}, false)

// node
process.on('unhandledRejection', (reason, promise) => {
  console.log(reason, promise)

  //reason => Promise 失败缘由,通常是一个错误对象
  //promise => 出现异常的Promise对象
})

Promise静态方法

类型转换 —— Promise.resolve()

静态方法 Promise.resolve(value) 能够认为是 new Promise() 方法的快捷方式。

Promise.resolve(42)
//等同于
new Promise(function (resolve) {
  resolve(42)
})

若是接受的是一个promise对象,那么这个对象会原样返回

const promise2 = Promise.resolve(promise)
console.log(promise === promise2) // true

若是传入的是一个对象,且这个对象也有一个then方法,传入成功和失败的回调,那么在后面执行的时候,也是能够按照promisethen来拿到。

(这个then方法,实现了一个thenable的接口,便可以被then的对象)

使用场景
  1. 能够是把第三方模拟promise库转化成promise对象
Promise.reslove({
    then: function(onFulfilled, onRejected) {
        onFulfilled('foo')
    }
})
.then(function (value) {
    console.log(value) // foo
})
  1. 直接将数值转换成promise对象返回
function test (bool) {
    if (bool) {
        return new Promise((resolve,reject) => {
            resolve(30) 
        })
    } else {
        return Promise.resolve(42)
    }
}
test(1).then((value) => {
    console.log(value)
})

Promise.reject()

Promise.reject(error) 是和 Promise.resolve(value) 相似的静态方法,是 new Promise() 方法的快捷方式。

建立一个必定是失败的promise对象

Promise.reject(new Error('出错了'))
//等同于
new Promise(function (resolve) {
  reject(new Error('出错了'))
})

数据聚合 —— Promise.all()

若是须要同时进行多个异步任务,使用promise静态方法中的all方法,能够把多个promise合并成一个promise统一去管理。

Promise.all(promiseArray);

  • Promise.all 生成并返回一个新的 Promise 对象,因此它可使用 Promise 实例的全部方法。参数传递promise数组中全部的 Promise 对象都变为resolve的时候,该方法才会返回, 新建立的 Promise 则会使用这些 promise 的值。
  • 参数是一个数组,元素能够是普通值,也能够是一个promise对象,输出顺序和执行顺序有关,
  • 该函数生成并返回一个新的 Promise 对象,因此它可使用 Promise 实例的全部方法。参数传递promise数组中全部的 Promise 对象都变为resolve的时候,该方法才会返回完成。只要有一个失败,就会走catch
  • 因为参数数组中的每一个元素都是由 Promise.resolve 包装(wrap)的,因此Paomise.all 能够处理不一样类型的 promose 对象。
var promise = Promise.all([
    // ajax函数是一个异步函数并返回promise,不须要关心哪一个结果先回来,由于是都完成以后整合操做
    ajax('/api/users.json'),
    ajax('/api/posts.json')
])

Promise.then(function(values) {
    console.log(values) //返回的是一个数组,每一个数组元素对应的是其promise的返回结果
}).catch(function(error) {
    console.log(error) // 只要有一个失败,那么就会总体失败走到catch里面
})

竞争 —— Promise.race()

Promise.race(promiseArray);

all同样会接收一个数组,元素能够是普通值也能够是promise对象,和all不一样的是,它只会等待第一个结束的任务

// 下面的例子若是request超过了500ms,那么就会报超时错诶,若是小于500ms,则正常返回。
const request = ajax('/api/posts.json')
const timeout = new Promise((resovle, reject) => {
    setTimeout(() => reject(new Error('timeout')), 500)
})

Promise.race([
    request,
    timeout
])
.then(value => {
    console.log(value)
})
.catch(error => {
    console.log(error)
})

Promise执行时序 —— 宏任务 vs 微任务

执行顺序 : 宏任务 => 微任务 => 宏任务

微任务promise以后才加入进去的,目的是为了提升总体的响应能力

咱们目前绝大多数异步调用都是做为宏任务执行, promise的回调 & MutationObserver & node中的 process.nextTick会做为微任务执行

下面的例子,当前宏任务当即执行,then是微任务会延后执行,setTImeout是异步的一个宏任务也会延后执行。当前宏任务执行完毕以后,微任务会先执行完毕以后下一个宏任务才会执行。

console.log('global start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)
Promise.resolve()
    .then(( => {
        console.log('promise')
    }))
    .then(( => {
        console.log('promise2')
    }))
    .then(( => {
        console.log('promise3')
    }))

console.log('global end')

// global start
// global end
// promise
// promise2
// promise3
// setTimeout

具体的牵扯到eventLoop的东西以后再进一步探讨。

深度剖析:手写一个Promise源码

深度剖析:手写一个Promise源码

ES6-ES10学习版图

说实话这个是最近比较复杂的一个笔记了,给本身点个赞,标个特殊标记。