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

说明

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

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

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

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

Web 应用的异步编程模型

在这里插入图片描述

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 请求封装一下:

封装请求过程

在这里插入图片描述

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 的返回值穿透到最外层

在这里插入图片描述

合并多个错误处理

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 来捕获异常。

在这里插入图片描述

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

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

拓展:unhandledrejection

MDN:unhandledrejection

在这里插入图片描述

基本的异常上报

在这里插入图片描述

防止默认处理

在这里插入图片描述

模拟实现一个 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 还没有执行

在这里插入图片描述

4.改造 Bromise 中的 resolve 方法

让 resolve 延迟调用 onResolve_

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

实现如下

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

在这里插入图片描述

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

重点理解

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

其他

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凯小默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值