浏览器原理 18 # Promise 到底解决了什么问题呢?

简介: 浏览器原理 18 # Promise 到底解决了什么问题呢?

说明

浏览器工作原理与实践专栏学习笔记



Promise 到底解决了什么问题呢?

Promise 解决的是异步编码风格的问题,而不是一些其他的问题


异步编程的问题:代码逻辑不连续

Web 应用的异步编程模型


20210423153411120.png


Web 页面的单线程架构决定了异步回调

下面的 XMLHttpRequest 例子:出现多次回调,这导致逻辑不连贯、不线性,非常不符合人的直觉

//执行状态
function onResolve(response){console.log(response) }
function onReject(error){console.log(error) }
let xhr = new XMLHttpRequest()
xhr.ontimeout = function(e) { onReject(e)}
xhr.onerror = function(e) { onReject(e) }
xhr.onreadystatechange = function () { onResolve(xhr.response) }
//设置请求类型,请求URL,是否同步信息
let URL = 'https://time.geekbang.com'
xhr.open('Get', URL, true);
//设置参数
xhr.timeout = 3000 //设置xhr请求的超时时间
xhr.responseType = "text" //设置响应返回的数据格式
xhr.setRequestHeader("X_TEST","time.geekbang")
//发出请求
xhr.send();


封装异步代码,让处理流程变得线性

将上面的 XMLHttpRequest 请求封装一下:


封装请求过程

20210423161239725.png


1.输入的 HTTP 请求信息全部保存到一个 request 的结构中

//makeRequest用来构造request对象
function makeRequest(request_url) {
    let request = {
        method: 'Get',
        url: request_url,
        headers: '',
        body: '',
        credentials: false,
        sync: true,
        responseType: 'text',
        referrer: ''
    }
    return request
}


2.封装 XFetch 函数

//[in] request,请求信息,请求头,延时值,返回类型等
//[out] resolve, 执行成功,回调该函数
//[out] reject  执行失败,回调该函数
function XFetch(request, resolve, reject) {
    let xhr = new XMLHttpRequest()
    xhr.ontimeout = function (e) { reject(e) }
    xhr.onerror = function (e) { reject(e) }
    xhr.onreadystatechange = function () {
        if (xhr.status = 200)
            resolve(xhr.response)
    }
    xhr.open(request.method, URL, request.sync);
    xhr.timeout = request.timeout;
    xhr.responseType = request.responseType;
    //补充其他请求信息
    //...
    xhr.send();
}


3.业务代码编写

XFetch(makeRequest('https://time.geekbang.org'),
  function resolve(data) {
      console.log(data)
  }, function reject(e) {
      console.log(e)
})


新的问题:回调地狱

先看一个例子:该例子是基于上面的

XFetch(makeRequest('https://time.geekbang.org/?category'),
  function resolve(response) {
      console.log(response)
      XFetch(makeRequest('https://time.geekbang.org/column'),
          function resolve(response) {
              console.log(response)
              XFetch(makeRequest('https://time.geekbang.org')
                  function resolve(response) {
                      console.log(response)
                  }, function reject(e) {
                      console.log(e)
                  })
          }, function reject(e) {
              console.log(e)
          })
  }, function reject(e) {
      console.log(e)
})


我们可以看到这个代码看起来很乱,不直观,它用了嵌套调用,并且都要进行错误的处理。

那么怎么处理这种问题?

  • 消灭嵌套调用
  • 合并多个任务的错误处理



Promise:消灭嵌套调用和多次错误处理

1.使用 Promise 来重构 XFetch

// 引入了 Promise,在调用 XFetch 时,会返回一个 Promise 对象
// 业务流程都在 executor 函数中执行
function XFetch(request) {
  function executor(resolve, reject) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', request.url, true)
      xhr.ontimeout = function (e) { reject(e) }
      xhr.onerror = function (e) { reject(e) }
      xhr.onreadystatechange = function () {
          if (this.readyState === 4) {
              if (this.status === 200) {
                  // 执行成功了,会调用 resolve 函数,触发 promise.then 设置的回调函数
                  resolve(this.responseText, this)
              } else {
                  let error = {
                      code: this.status,
                      response: this.response
                  }
                  // 执行失败了,则调用 reject 函数时,触发 promise.catch 设置的回调函数
                  reject(error, this)
              }
          }
      }
      xhr.send()
  }
  return new Promise(executor)
}


2.利用 XFetch 来构造请求流程

var x1 = XFetch(makeRequest('https://time.geekbang.org/?category'))
var x2 = x1.then(value => {
    console.log(value)
    return XFetch(makeRequest('https://www.geekbang.org/column'))
})
var x3 = x2.then(value => {
    console.log(value)
    return XFetch(makeRequest('https://time.geekbang.org'))
})
x3.catch(error => {
    console.log(error)
})

相比上面的嵌套回调,这样看起来就线性直观很多。



Promise 是如何消灭嵌套回调和合并多个错误处理


解决嵌套回调


1.Promise 实现了回调函数的延时绑定

//创建Promise对象x1,并在executor函数中执行业务逻辑
function executor(resolve, reject){
    resolve(100)
}
let x1 = new Promise(executor)
//x1延迟绑定回调函数onResolve
function onResolve(value){
    console.log(value)
}
x1.then(onResolve)


2.将回调函数 onResolve 的返回值穿透到最外层

20210425100936542.png


合并多个错误处理

function executor(resolve, reject) {
    let rand = Math.random();
    console.log(1)
    console.log(rand)
    if (rand > 0.5)
        resolve()
    else
        reject()
}
var p0 = new Promise(executor);
var p1 = p0.then((value) => {
    console.log("succeed-1")
    return new Promise(executor)
})
var p3 = p1.then((value) => {
    console.log("succeed-2")
    return new Promise(executor)
})
var p4 = p3.then((value) => {
    console.log("succeed-3")
    return new Promise(executor)
})
p4.catch((error) => {
    console.log("error")
})
console.log(2)


将代码放到控制台运行,随机的一个结果如下:无论哪个对象里面抛出异常,都可以通过最后一个对象 p4.catch 来捕获异常。


2021042510300638.png


因为 Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被 onReject 函数处理或 catch 语句捕获为止。


   参考:promise 内部有 resolved_ 和 rejected_ 变量保存成功和失败的回调,进入 .then(resolved,rejected) 时会判断 rejected 参数是否为函数,若是函数,错误时使用 rejected 处理错误;若不是,则错误时直接 throw 错误,一直传递到最后的捕获,若最后没有被捕获,则会报错。可通过监听 unhandledrejection 事件捕获未处理的 promise 错误。


拓展:unhandledrejection

MDN:unhandledrejection

20210425112624486.png


基本的异常上报

20210425112844145.png


防止默认处理

20210425112911171.png


模拟实现一个 Promise

下面将模拟的对象称为 Bromise

1.Bromise 的实现代码

function Bromise(executor) {
    var onResolve_ = null
    var onReject_ = null
     //模拟实现resolve和then,暂不支持rejcet
    this.then = function (onResolve, onReject) {
        onResolve_ = onResolve
    };
    function resolve(value) {
          //setTimeout(()=>{
            onResolve_(value)
           // },0)
    }
    executor(resolve, null);
}


2.使用 Bromise 来实现业务代码

function executor(resolve, reject) {
    resolve(100)
}
//将Promise改成我们自己的Bromsie
let demo = new Bromise(executor)
function onResolve(value){
    console.log(value)
}
demo.then(onResolve)


3.执行代码

把代码放到控制台执行发现报错了:

由于 Bromise 的延迟绑定导致的,在调用到 onResolve_ 函数的时候,Bromise.then 还没有执行

20210425110859770.png



4.改造 Bromise 中的 resolve 方法


让 resolve 延迟调用 onResolve_


比如:采用定时器(效率并不是太高)来推迟 onResolve 的执行,

实现如下

function resolve(value) {
  setTimeout(()=>{
    onResolve_(value)
  },0)
}


20210425111818137.png


Promise 把这个定时器改造成了微任务了,这样既可以让 onResolve_ 延时被调用,又提升了代码的执行效率



重点理解

Promise 通过回调函数延迟绑定、回调函数返回值穿透、错误“冒泡”技术这三个点。


其他

到时手写系列将会在面试专栏那里出现:比如(手写一个 Promise )。

目录
相关文章
|
6月前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
前端开发
Promise的用法&原理&手写实现-2
Promise的用法&原理&手写实现-2
40 1
|
1月前
|
前端开发 JavaScript 异构计算
简述浏览器的渲染原理
浏览器渲染原理主要包括以下步骤:1)解析HTML文档生成DOM树;2)解析CSS生成CSSOM树;3)结合DOM与CSSOM生成渲染树;4)布局计算(回流)确定元素大小和位置;5)绘制(Paint)将节点转为图形内容;6)合成(Composite)多层图像。整个过程从文档解析到最终输出完整网页,并通过优化技术提升性能。
|
6月前
|
Web App开发 JavaScript 前端开发
浏览器与Node.js事件循环:异同点及工作原理
浏览器与Node.js事件循环:异同点及工作原理
|
5月前
|
JavaScript 前端开发 网络协议
浏览器的工作原理
主要分为导航、获取数据、HTML解析、css解析、执行javaScript、渲染树几个步骤。
63 1
|
4月前
|
缓存 JavaScript 前端开发
前端 JS 经典:浏览器中 ESModule 的工作原理
前端 JS 经典:浏览器中 ESModule 的工作原理
47 0
|
5月前
|
存储 前端开发 API
技术笔记:Promise的原理探究及手写Promise
技术笔记:Promise的原理探究及手写Promise
34 0
|
6月前
|
存储 前端开发 安全
快速了解std::promise的工作原理和使用
快速了解std::promise的工作原理和使用
185 3
|
6月前
|
前端开发 JavaScript API
Promise.all() 的原理与实战:简化异步逻辑的不二选择
Promise.all() 的原理与实战:简化异步逻辑的不二选择
Promise.all() 的原理与实战:简化异步逻辑的不二选择
|
6月前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
301 0

热门文章

最新文章