Promise的用法&原理&手写实现-2

简介: Promise的用法&原理&手写实现-2

5.11 catch方法-异常穿透与值传递

catch 方法是获取失败的值,因为前面 then() 方法 已经写的很完善了,所以 catch 只要调用一下 then() 就好



index.html

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('Err');
    }, 1000)
})
let res = p.catch(reason => {
    console.log(reason);
    return 321;
})
console.log(res);


promise.js

Promise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected);
}



接下来就是要完成 catch 方法的异常穿透效果

我按照顺序写下来:

index.html

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('Err');
    }, 1000)
})
let res = p.then(value => {
    console.log(111);
}).then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.log(reason);
})



这时候会出现这个问题:

说 onRejected 方法不存在,这是什么情况?


解释:


executor 执行器在执行它内部异步代码前,同步代码已经执行结束了,也就是 p.then() 执行完毕,把 then 内的回调方法存到了 p 的自身属性上。而 then 的回调参数有两个:onResolved,onRejected。在这个案例中只传了一个 onResolve,没有onRejected,所以 保存在 p 本身上的回调函数 onRejected 就为空。所以我们要主动给 then 方法内加上一个 onRejected 回调函数。

Promise.prototype.then = function (onResolved, onRejected) {
    // 判断回调函数的参数
    // then 方法中并没有 onRejected 这个回调方法
    if (typeof onRejected !== 'function') {
        // 手动给 then 添加这个回调函数 
        onRejected = reason => {
            // 抛异常
            throw reason;
        }
    }
}

这时候在最后的 catch 就能接受到这个异常了

值传递的话就是 then 方法连 onResolved 回调函数都不传递了。

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('Err');
    }, 1000)
})
let res = p.then().then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.log(reason);
})

这时候处理方法和上面的类似,加一个 onResolved 回调就行

// then 方法中并没有 onResolved 这个回调方法
if (typeof onResolved !== 'function') {
    // 手动给 then 添加这个回调函数 
    onResolved = value => {
        return value;
    }
}

5.12 resolve 方法封装

比较简单直接亮代码

Promise.resolve = function (value) {
    // 返回 promise 对象
    return new Promise((resolve, reject) => {
        if (value instanceof Promise) {
            value.then(v => {
                resolve(v);
            }, r => {
                reject(r);
            })
        } else {
            resolve(value);
        }
    })
}

.13 all 方法封装

index.html

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Ok')
    }, 1000)
})
let p2 = new Promise((resolve, reject) => {
    resolve('success')
});
let p3 = new Promise((resolve, reject) => {
    resolve('dddd')
});
let res = Promise.all([p1, p2, p3])
console.log(res);

promise.js

Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        // 声明变量
        let count = 0;
        let arr = [];
        // 遍历
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(v => {
                // 对象的状态是成功
                count++;
                // 将当前 promise 对象成功的结果 存到数组中
                arr[i] = v;
                // 判断如果全都成功,就返回成功
                if (count === promises.length) {
                    // 修改状态
                    resolve(arr);
                }
            }, r => {
                reject(r);
            })
        }
    })
}

5.14 then 方法 回调的异步执行

先看场景:

index.html

let p1 = new Promise((resolve, reject) => {
    // setTimeout(() => {
    //     resolve('Ok')
    // }, 1000)
    resolve('OK')
    console.log(111);
})
p1.then(value => {
    console.log(222);
})
console.log(333);

要求我们的 then 内部的回调方法应该是异步执行。

打印结果:却是同步执行

用一个比较粗糙的方法解决,给所有的回调函数加上定时器:

总结分析:


这个尚硅谷的视频一直没讲链式调用的过程,我觉得是一个很大的遗憾,我这一块还是挺迷糊的,我尝试着自己来分析一下。

首先我们需要知道 then 本身是同步,只是它内部的回调函数是异步的。可以用代码来测试一下

index.html

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Ok')
    }, 1000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 2000)
});
let p3 = new Promise((resolve, reject) => {
    resolve('dddd')
});
p1.then(value => {
    console.log(value);
    console.log(111);
    return p2;
}).then(value => {
    console.log(value);
    console.log(222);
    return p3;
}).then(value => {
    console.log(value);
    console.log(333);
})
console.log('我得在回调之前执行');


promise.js

修改 then 函数内容,在调用 then 时一上来就打印一下。

Promise.prototype.then = function (onResolved, onRejected) {
    console.log('then本身是同步的');
}


我们可以看一下打印结果:

可以得出结论,then 的链式调用会在一开始就一下子执行下去。then 内部的回调函数是按照我们写的顺序执行下去,为啥会这样呢,我们一行一个代码逐步解析一下。

先分析一上来定义出来的三个 promise 对象

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Ok')
    }, 1000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 2000)
});
let p3 = new Promise((resolve, reject) => {
    resolve('dddd')
});


第一步:定义 P1 是一个 promise 对象,P1 的 PromiseState 是 pending,PromiseResult 是 null,this.callback = []。然后开始调用 executor(resolve, reject) 执行器, 但执行器内部一进去就是一个定时器,定时器是异步函数调用,进异步队列,然后继续往下执行就到 P2。



34670437a88542c9995758ddffb78ee2.png

第二步:定义 P2a892923ff35d4bf08f7318a739f752f5.png,P2 与 P1 类似, PromiseState 和 PromiseResult 都一样,executor 执行器内部也是一个定时器,进异步队列


第三步:定义 P3,因为 P3 executor 执行器 内部没有异步函数,所以直接按照同步代码执行,而且调用的是 resolve,所以返回的是成功 PromiseState 为 fulfilled, PromiseResult 为 Ok ,callback 是 异步函数,虽然里面还没有值,还是要放到异步队列中。


开始比较刺激的 then 方法调用了。


7e4ecba1e2c04b089679c334b5e09756.png


第四步:P1 调用 then 方法,这个过程是这样的,因为我们之前在 then 的第一行添加了console.log('then本身是同步的'); ,所以控制台打印 then本身就是同步的。 接着因为 P1 的 PromiseState 是 pending ,所以会将 then 中的回调函数保存到 P1 的 callback 属性上。这时 P1.then() 方法执行结束,返回一个 Promise 对象,我把该返回的 Promise 对象命名为 X。该Promise 对象正处于 pending 状态中。



e48b3774abab4b8dbc8984e11ade1acb.png


9b0bcc323e03401fa77e8c24ede26d43.png


第五步:链式调用 then。

这个就相当于 X 在调用 then


X 此时 是 pending 状态,调用 then 就是 先打印下 then本身是同步的 这句话,然后就是 把 then 内部的回调函数保存 X 的 callback 上。这时, X.then() 执行结束, X.then() 返回一个对象,我称之为 Y ,这个 Y 对象状态此时也是 pending。


61db1ac287b3429ead895e8d1f4d2d86.png

第六步:最后一个链式调用

p1.then(value => {
    console.log(value);
    console.log(111);
    return p2;
}).then(value => {
    console.log(value);
    console.log(222);
    return p3;
}).then(value => {
    console.log(value);
    console.log(333);
})

分解后相当于:

let X = p1.then(value => {
    console.log(value);
    console.log(111);
    return p2;
})
let Y = X.then(value => {
    console.log(value);
    console.log(222);
    return p3;
})
let Z = Y.then(value => {
    console.log(value);
    console.log(333);
})

就和上面一样,Y 此时是 pending 状态,所以调用 then 方法,直接将回调函数保存到 Y 的 callback 属性上,返回一个新 Promise ,状态是 pending。



0288674e12b94b74a2bd72c4c8b0aaf8.png

这是同步代码执行完,控制台打印的结果:


4d0f2722ed814ad9af0d4124ebee4d46.png


到此为止,所有同步代码执行完毕,开始执行异步代码,这一部分又是老大难。。

第七步:开始执行异步队列的代码

异步队列里有三个异步函数, 其实 setTimeout-回调-P3 这个异步函数并没有作用,因为 P3 的 callback 是空的。有作用的就 setTimeout-P1 和 setTimeout-P2。


因为只有 P1.then() 调用了,存有属于自己的回调函数,别的 P2,P3 都没有 .then() 拥有自己的回调函数。所以就算 P2 或 P3 的执行器里的 异步函数先完成,他们的 callback 也是空的,没有回调函数可以调用。

接下来直接讲解下这个调用过程。

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Ok')
    }, 1000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 2000)
});
let p3 = new Promise((resolve, reject) => {
    resolve('dddd')
});
let X = p1.then(value => {
    console.log(value);
    console.log(111);
    return p2;
})
let Y = X.then(value => {
    console.log(value);
    console.log(222);
    return p3;
})
let Z = Y.then(value => {
    console.log(value);
    console.log(333);
})
console.log('X', X);
console.log('Y', Y);
console.log('Z', Z);
console.log('P1', p1);
console.log('P2', p2);
console.log('P3', p3);


还没执行前所有对象的状态

我们来看一下执行的步骤过程。


第一步:先执行完 P1 的 resolve 方法(P1 异步函数在执行时会记住 P1 的环境),将 P1 的属性都成功更改为 fulfilled 和 Ok,然后开始执行 P1 callback 里保存的回调函数。

这个回调函数是

这个回调时被修饰过的,修饰后:




216cc77b3a654661840dd7c0a42e6137.png


执行后结果是,打印 value,打印 111,将 P2 返回给 res



0d2a06f60cb2405b8763cbf774cb7069.png



P1.then() 执行结束时返回了一个 Promise 对象给 X。而 res 执行的环境是Promise X 的环境

0e92d26f542c454baa0f583841e437bf.png



resolve 执行就是修改 X 的状态,X 更改为 fulfilled,内容为 success ,然后要执行 X 的 callback 里的回调函数。X callback里的回调函数是 X.then() 时保存的。

然后就和上面的过程一样。

最终结果:一切都顺利执行。


然后就和上面的过程一样。

最终结果:一切都顺利执行。

本质:


尚硅谷的 promise 的实现研究后发现就是在输出结果上模拟了真实 promise 的实现过程。但实现过程不是真的 promise。如果存在这么一个场景,p2 的执行需要用到 p1 完成后的结果,那尚硅谷模拟的 promise 是无法完成的。因为 尚硅谷的 promise 只是把 p1,p2,p3的结果按顺序打印出来而已,实际上三者的异步方法在执行过程中是无关的。

6. Promisification

指将一个接受回调的函数转换为一个返回 promise 的函数。


由于许多函数和库都是基于回调的,因此,在实际开发中经常会需要进行这种转换。因为使用 promise 更加方便,所以将基于回调的函数和库 promisify 是有意义的。(译注:promisify 即指 promise 化)


具体案例可以看 2.3 2.4 的练习

7. 简介 async await

7.1 async


  • 函数的返回值为 promise 对象。
  • promise 对象的结果由 async 函数执行的返回值决定

7.2 await


  • await 右侧的表达式一般为 promise 对象,但也可以是其他的值
  • 如果表达式 promise 对象。await 返回的是 promise 成功的值
  • 如果表达式是其它值,直接将此值作为await 的值

7.3 实际案例1


const fs = require('fs')
const util = require('util')
// promisify 处理完返回的是一个 promise 对象
const mineReadFile = util.promisify(fs.readFile);
async function main() {
    // 读取第一个文件的内容
    let data1 = await mineReadFile('./a.txt');
    let data2 = await mineReadFile('./b.txt');
    let data3 = await mineReadFile('./c.txt');
}

7.4 实际案例2

function sendAJAX(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.send();
        // 处理结果
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr.status)
                }
            }
        }
    })
}
let btn = document.querySelector('#btn');
btn.addEventListener('click',async function () {
    // 获取信息
    let message = await sendAJAX('https://api.apiopen.top/getJoke');
    console.log(message);
})


目录
相关文章
|
3月前
|
缓存 前端开发 JavaScript
整会promise这8个高级用法,再被问倒来喷我
整会promise这8个高级用法,再被问倒来喷我
整会promise这8个高级用法,再被问倒来喷我
|
3月前
|
前端开发 JavaScript API
Promise.all() 的原理与实战:简化异步逻辑的不二选择
Promise.all() 的原理与实战:简化异步逻辑的不二选择
Promise.all() 的原理与实战:简化异步逻辑的不二选择
|
4月前
|
前端开发 JavaScript
使用promise解决异步请求的用法
使用promise解决异步请求的用法
34 0
|
6月前
|
前端开发 JavaScript API
Promise的用法&原理&手写实现-1
Promise的用法&原理&手写实现-1
22 0
|
8月前
|
前端开发
promise ,async/await的基础用法
promise ,async/await的基础用法
|
11月前
|
存储 缓存 自然语言处理
吊打面试官:promise原理详解
吊打面试官:promise原理详解
157 0
|
前端开发 JavaScript API
【温故知新】5 个 Promise 要避免的常见用法~
本瓜一直觉得 Promise 就是咱 JS 人的浪漫,没错,Promise 天天见,但或许越熟悉越陌生,我们在一直用的过程中会形成一些定式,这导致难免会漏掉一些定式以外的要点;
|
前端开发
重新手写promise,理解核心的异步链式调用原理
重新手写promise,理解核心的异步链式调用原理
147 0
|
前端开发
promise的简单用法、使用、例子、另外处理方法
promise的简单用法、使用、例子、另外处理方法
87 0
|
前端开发 JavaScript
通过polyfill理解Promise原理
一个挺经典的前端面试题,自己polyfill实现Promise。在网友实现的基础上,自己理解加上注释。 这个问题涉及到了JavaScript作用域规则、事件循环、函数上下文、原型继承等诸多基础知识,理解完感觉很有收获,以注释形式记录下来。