Promise 执行机制

您所在的位置:网站首页 promise的汉语意思 Promise 执行机制

Promise 执行机制

2023-09-19 10:51| 来源: 网络整理| 查看: 265

Promise 执行机制——共有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败) & 宏任务(主线) 微任务(穿插)

深入探讨 Promise 之前,有个概念先交代一下,有助于对后文进行了解。

微任务

通常,我们把消息队列中的任务成为宏任务,而每一个宏任务中都包含了一个微任务队列。等到当前宏任务中的主要任务执行完成了以后,渲染引擎其实并不着急执行下一个宏任务,而是先去执行当前宏任务中的微任务。

只是引入了概念,我想大家会跟我有同样的疑惑,平常大部分的开发中,宏任务就能满足需求了,为什么要引入微任务呢?

现在来设想一下,在的消息队列中,可能随时被添加:页面渲染事件、各种IO的完成事件,执行 JavaScript 脚本事件、用户交互事件等等,而添加事件是由系统操作的,JavaScript 并不能准确的掌控任务要添加到队列的位置,进而也没法掌控开始执行任务的时间。举个例子:

function time2() { for (let a = 0; a for (let a = 0; a 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(requesthod, URL, request.sync); xhr.timeout = request.timeout; xhr.responseType = request.responseType; //... xhr.send(); }

感觉挺完美,在业务逻辑比较简单的情况下也能很好的满足需求,但是,一旦功能变得复杂起来,会发现面临新的问题—回调地狱。

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 重构 XFetch 的代码

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(this.responseText, this); } else { let error = { code: this.status, response: this.response }; reject(error, this) } } } xhr.send() } return new Promise(executor) }

这个时候再利用 XFetch 来构造请求流程,就变的非常容易了

const 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 是怎么解决第一个问题(嵌套回调)的呢?

首先,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);

还记得在文章开头提到的微任务吗? 提到了延时绑定的概念,现在就可以把它与微任务串起来了。

当要执行 Promise 中的 reslove 函数的时候就会触发 x1.then 中设置的 onResolve,由于 Promise 使用了延时绑定技术,所以在执行 resolve 函数的时候,回调函数还没有绑定,只能推迟回调函数的执行,如果要是使用定时器的话,我们都知道定时器是一个宏任务,效率并不高,所以,Promise 为了提升代码的执行效率,就改造成了微任务,这就是 Promise 中使用 微任务的原因。

其次,需要将回调函数 onResolve 的返回值穿透到最外层。

// 创建Promise对象x1,并在executor函数中执行业务逻辑 function executor(resolve, reject) { resolve(100) } let x1 = new Promise(executor); //x1延迟绑定回调函数onResolve function onResolve(value) { console.log(`onResolve 中的 value:${value}`); const x = new Promise((resolve, reject) => { resolve(value + 1); }) console.log('onResolve 中新建的 Promise:'); console.log(x); return x; } let x2 = x1.then(onResolve); console.log(x2); x2.then((value) => { console.log(`x2.then中的value:${value}`); console.log(`x2.then中的x2: `); console.log(x2); })

打印结果如下图所示:

在这里插入图片描述

这样大概明白了,Promise 是怎么实现回调函数穿透的:

创建 Promise 执行结果先保存在 x1 变量中,然后 x1.then 执行的 onResolve() 返回值为new Promise((resolve,reject)⇒{…}),也就是说,当前 x2 = new Promise(resolved),这样,就实现了回到函数返回值穿透到最外层的工作。同样基于这个原因,Promise可以采用链式编程,即 then 方法后面再调用另一个 then 方法。

由此,我们知道了,Promise 通过回调函数的延时绑定和回调函数返回值穿透解决了多层嵌套的问题。

那么,Promise 应该怎么处理异常才能合并多个任务的错误处理呢?

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

Promise 对象的错误就有"冒泡"性质,会一直向后传递,直到被 onReject 或 catch 语句捕获为止,所以,上述例子中,P0~P4 无论哪个对象抛出异常,都可以通过最后一个对象 P4.chatch 来捕获。这样就不需要每个 Promise 对象单独捕获异常了。

那么,Promise 对象的错误又是如何具有"冒泡"性质的呢?

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

window.addEventListener("unhandledrejection", event => { console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`); });

如果 Promise 状态已经变成了 resolved,再抛出就是无效的了。

const promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test'); }); promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }); // ok 如何取消 Promise

很多人在最初学习 Promise 的时候会想要知道应该如何取消 Promise。这里有很多人在试图取消 Promise 时常犯的错误:

试图将 .cancel() 添加到 Promise 中。 const promise = new Promise(function(resolve, reject) { resolve('ok'); }); // 注意,Promise 并不支持这种写法 promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }) .cancel(function(cancel) { console.log(cancel) }); 忘记清理 有些人将 Promse.race() 用作取消机制。 const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); p .then(console.log) .catch(console.error);

在5秒之内没有返回结果,Promise 的状态就会变成 reject,否则就是 resolved。这样可能存在隐患,取的取消控制权的地方是在创建 Promise 的地方,这里又是你唯一能进行适当清理数据的地方,比如说,清除超时或通过清除对数据的引用来释放内存。很多时候会忘记对添加的定时器等进行清理。

忘记对拒绝 cancel 进行处理过于复杂

重新考虑取消 Promise 的实现方式

通常,我们使用默认参数告诉 Promise,在默认情况下不取消,不过当我们捕获到超时的 ID 时可以取消他。我们使用 cancel.then() 方法来处理取消和资源清理,只有在 Promise 状态更改为 fulfilled 或 rejected 之前才有机会取消,否则毫无意义

function cancelPromise(promise, token) { Promise.race([ promise, new Promise((_, reject) => { token.cancel = function cancel(reason) { reject(reason); }; }) ]); } const cancelToken = { cancel() { } }; cancelPromise(fetch('url'), cancelToken); cancelToken.cancel('成功取消');


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3