概要

本文主要介绍了Promise上定义的api的特性,以及手写如何实现这些特性。目的是把学习过程中的产出以博客的方式输出,巩固知识,也便于之后复习

博客思路

mdn上搜索Promise,了解类和api的定义:

  • 定义了哪些属性,分别代表什么含义
  • api需要传什么参数,返回什么值,可能抛出什么异常
  • 看官方给出的用例,猜想内部可能的实现
  • 编写源码,用官方用例验证查看返回值是否一致

API的特性与手写源码

构造函数

  • promise有状态pending、rejectedresolved,所以应该有个变量来保存状态
  • 构造函数参数excutor是个同步执行的回调函数,函数执行的参数是两个函数resolved和rejected,所以promise内部需要定义两个函数,并且在构造行数中执行excutor的地方传入
  • .then中会传入回调函数onResolved和onRejected,在resolved和rejected内会分别会触发对应的回调函数,所以需要两个数组保存then中传进来的回调
  • resolved和rejected只能执行一次,执行后promise的状态会改变,且参数会传递给回调函数
  • onRejected和onResolved异步执行
  • excutor执行抛异常会直接执行rejected,所以excutor的执行需要catch错误

const PENDING = "PENDING";
const RESOLVED = "resolved";
const REJECTED = "rejected"; function MyPromise(excutor){ // promise内部保存着状态值
this.status = PENDING;
this.data = null;
// then方法传进来的回调函数此保存
this.onResolvedList = [];
this.onRejectedList = []; let resolved = (value) => {
// resolved函数只能执行一次,所以先判断状态是不是pending
if(this.status !== PENDING){
return;
}
// 变更状态为resolved
this.status = RESOLVED;
// 数据为传进来的值
this.data = value; // 判断是否已经有onResolved回调已经穿入,有则异步执行
if(this.onResolvedList.length > 0){
setTimeout(() => {
this.onResolvedList.forEach(onResolved => {
onResolved(value);
});
}, 0);
}
} let rejected = (reason) => {
if(this.status !== PENDING){
return
} this.status = REJECTED;
this.data = reason; if(this.onRejectedList.length > 0){
setTimeout(() => {
this.onRejectedList.forEach(onRejected => {
onRejected(reason);
});
});
}
} try{
// 执行器函数同步执行,且参数为promise内定义的resolve和rejected
excutor(resolved, rejected);
}catch(error){
// 如果执行器函数出错直接执行rejected
this.rejected(error);
}
}

then

  • then会接受两个回调函数onResolved和onRejected
  • onResolved和onRejected会异步调用
  • then会返回一个新的promise对象
  • then的参数如果没传,那么value和reason继续向下传递
  • 如果执行then的时候,promise的状态还是pending,那么只保存回调,并且确保回调执行后能修改新的promise的状态
  • 如果触发的对应的回调函数执行抛异常,那么返回的新的回调函数状态为rejected,reason则会catch到的error
  • 如果触发的对应回调函数执行返回值不是promise对象,那么返回新的promise状态为resolved,value则为传入then的回调的返回值
  • 如果触发的对应回调返回值是promise对象,那么新的promise返回值的状态取决于改回调返回的promise
MyPromise.prototype.then = function(onResolved, onRejected){

    // 如果没有传onResolved,则设置onResolved为返回value的函数
onResolved = typeof onResolved === "function" ? onResolved : value => value
// 如果没有传onRejected,则设置onRejected为抛处reason的函数
onRejected = typeof onRejected === "function" ? onRejected : reason => {throw reason} return new MyPromise((resolved, rejected) => { // 传入要执行的回调函数
let callBackExcu = (callback) => {
try{
let result = callback(this.data);
if(result instanceof MyPromise){
// 如果回调返回值还是promise则then返回的promise的状态取决于回调的返回的promise,成功就执行resolved失败就执行rejected
result.then(resolved, rejected);
}else{
// 如果回调的返回值不为promise则新的promise状态为resolved
resolved(result)
}
}catch(error){
// 如果回调执行抛处异常,则新的promise状态为rejected
rejected(error);
}
} if(this.status === PENDING){
// 如果状态为pending则保存回调且确保回调执行后能修改当前返回的promise的状态
this.onResolvedList.push((value) => {
callBackExcu(onResolved)
});
this.onRejectedList.push((reason) => {
callBackExcu(onRejected)
});
}else{
// 如果状态不为pending则根据状态执行对应的回调,且修改当前promise的状态
switch(this.status){
case REJECTED:
// onRejected异步执行
setTimeout(() => {
callBackExcu(onRejected);
});
break;
case RESOLVED:
// onResolved异步执行
setTimeout(() => {
callBackExcu(onResolved);
});
break;
}
}
});
}

catch

catch和then其实差不多,不同点在于传入的参数只有onRejected,所以

MyPromise.prototype.catch = function(onRejected){
// catch与then的不同点在于传入的参数不一样,不需要传onResolve
return this.then(null, onRejected);
}

Promise.resolved

  • resolved会返回一个promise对象
  • 如果传入的参数本就是一个primise对象则直接返回
  • 如果是一个包含“then”方法的对象,返回新的promise对象,且状态取决于then函数的执行,如果then的执行中抛错,则新的promise状态为rejected
  • then的参数为两个回调函数resolved和rejected
  • 如果传入参数value既不是promise的实例,也不是具备then函数的对象,则返回一个新的promise对象且改对象data就为value
MyPromise.resolve = function(value){
if(value instanceof MyPromise){
// 如果传入的参数本就是一个primise对象则直接返回
return value;
}else if(typeof value.then === "function"){
return new MyPromise((resolved, rejected) => {
try{
// then的参数为两个回调函数resolved和rejected
value.then(resolved, rejected);
}catch(error){
// 如果then的执行中抛错,则新的promise状态为rejected
rejected(error);
}
});
}else{
// 如果传入参数value既不是promise的实例
return new MyPromise((resolved, rejected) => {
resolved(value);
});
}
}

Promise.rejected

  • 接受参数reason,返回一个状态为rejected的data为reason的promise实例
MyPromise.reject = function(reason){
return new MyPromise((resolved, rejected) => {
rejected(reason);
});
}

Promise.all

  • 接收的参数是需要满足可迭代协议,否则会抛错
  • 返回值是个promise
  • 如果传入的参数是个空的可迭代的对象,则返回一个状态为resolved的可promise实例,data为空数组,
Promise.all([]) // Promise {<resolved>: Array(0)}
Promise.all("") // Promise {<resolved>: Array(0)}
  • 如果传入的参数中没有promise实例,或者所有的promise已经是resolved状态,则返回一个promise状态为pending,且异步更新为resolved
let p = Promise.all([1,2,3,4,Promise.resolve(5)])
console.log(p); // Promise {<pending>}
  • 如果存在promise且状态还是pending,返回一个promise实例,且等所有promise都resolved后,状态更新为resolved,data为传入的顺序

接下来看下源码

// 先定义一个验证参数是否满足可迭代协议的方法
const isIterable = function(object){
return typeof object[Symbol.iterator] === "function"
&& typeof object[Symbol.iterator]() === "object"
&& typeof object[Symbol.iterator]().next === "function"
} MyPromise.all = function(iterable){
if(!isIterable(iterable)){
// 不满足可迭代协议抛错
throw new TypeError("Object is not iterable");
} let data = [];
let count = 0;
// 迭代参数生成数组
let params = Array.from(iterable); return new MyPromise((resolved, rejected) => {
if(params.length === 0){
// 如果是空的可迭代对象,返回空数组
resolved(data);
}else{
params.forEach((element, index) => {
// 遍历每个参数,统一处理成promise的实例
// 这样就少了一个逻辑分支
let itemPromise = MyPromise.resolve(element);
itemPromise.then(
value => {
// data中的结果需要和传入参数的顺序一致
data[index] = value;
if(count === params.length - 1){
// 说明全都resolved了
resolved(data);
}
count++;
},
reason => {
// reject直接返回
rejected(reason);
}
);
});
}
});
}

Promise.race

  • 接收一个可迭代对象,这点与方法"all"相同
  • 返回一个新的promise
  • 返回的promise状态为pending,异步更新为resolved
let p = Promise.race([1,2,3,4]);
console.log(p); // Promise {<pending>} p.then(
value => {
console.log(value); // Promise{<resolved>: 1}
}
);
  • 传入的若干promise中,只要有一个promise最先resolved或者rejected,则返回的promise状态更新为resolved
let p1 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved("p1");
}, 10);
}); let p2 = new Promise((resolved, rejected) => {
setTimeout(() => {
resolved("p2");
}, 100);
}); let p = Promise.race([p2, p1]) p.then(
value => {
console.log(value); // p1
}
);

最后来看一下自己源码的实现

MyPromise.race = function(iterable){
if(!isIterable(iterable)){
// 不满足可迭代协议抛错
throw new TypeError("Object is not iterable");
} const params = Array.from(iterable); return new MyPromise((resolved, rejected) => {
params.forEach((element, index) => {
const itemPromise = MyPromise.resolve(element); itemPromise.then(
value => {
// 只要有一个promise resolved直接返回
resolved(value);
},
error => {
// 只要有一个promise rejected直接返回
rejected(error);
}
);
});
});
}

手写Promise看着一篇就足够了的更多相关文章

  1. 闭关修炼180天--手写IOC和AOP(xml篇)

    闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...

  2. 手写Promise A+ 规范

    基于ES6语法手写promise A+ 规范,源码实现 class Promise { constructor(excutorCallBack) { this.status = 'pending'; ...

  3. 一文全解:利用谷歌深度学习框架Tensorflow识别手写数字图片(初学者篇)

    笔记整理者:王小草 笔记整理时间2017年2月24日 原文地址 http://blog.csdn.net/sinat_33761963/article/details/56837466?fps=1&a ...

  4. 手写promise

    写在前面: 在目前的前端分开中,我们对于异步方法的使用越来越频繁,那么如果处理异步方法的返回结果,如果优雅的进行异步处理对于一个合格的前端开发者而言就显得尤为重要,其中在面试中被问道最多的就是对Pro ...

  5. 手写Promise中then方法返回的结果或者规律

    1. Promise中then()方法返回来的结果或者规律 我们知道 promise 的 then 方法返回来的结果值[result]是由: 它指定的回调函数的结果决定的 2.比如说下面这一段代码 l ...

  6. 前端面试题之手写promise

    前端面试题之Promise问题 前言 在我们日常开发中会遇到很多异步的情况,比如涉及到 网络请求(ajax,axios等),定时器这些,对于这些异步操作我们如果需要拿到他们操作后的结果,就需要使用到回 ...

  7. .NET、PHP、MySql、JS中的时间戳你每次是手写还是复制?这篇文章让你一次性搞懂

    什么是时间戳(chuō)? 答:时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数. 为什么时间戳要从1970年01月0 ...

  8. [转]史上最最最详细的手写Promise教程

    我们工作中免不了运用promise用来解决异步回调问题.平时用的很多库或者插件都运用了promise 例如axios.fetch等等.但是你知道promise是咋写出来的呢? 别怕-这里有本promi ...

  9. 手写 Promise

    在上一章节中我们了解了 Promise 的一些易错点,在这一章节中,我们会通过手写一个符合 Promise/A+ 规范的 Promise 来深入理解它,并且手写 Promise 也是一道大厂常考题,在 ...

随机推荐

  1. day7.关于字符串的相关操作

    一.字符串的相关操作 """ (1)字符串的拼接 (2)字符串的重复 (3)字符串跨行拼接 (4)字符串的索引 (5)字符串的切片: 语法 => 字符串[::] 完 ...

  2. 【小白学AI】八种应对样本不均衡的策略

    文章来自:微信公众号[机器学习炼丹术] 目录 1 什么是非均衡 2 8种解决办法 2.1 重采样(四种方法) 2.2 调整损失函数 2.3 异常值检测框架 2.4 二分类变成多分类 2.5 EasyE ...

  3. 新鲜整理的Java学习大礼包!!锵锵锵锵~

    第一部分:Java视频资源! 前端 HTML5新元素之Canvas详解 https://www.bilibili.com/video/BV1TE41177TE HTML5之WebStorage详解 h ...

  4. Android SQLite轻量级数据库的删除和查找操作

    今天主要是补充昨天的内容,本打算合成一章的,但是毕竟一天一天的内容写习惯了. 就这样继续昨天的,昨天只讲了创建以及增加和查询, 其实用法都差不多,今天学长也是在原有的基础上写的,还顺便融合了Share ...

  5. MySQL--->高级对象

    本章目标: 掌握MySQL视图 掌握MySQL存储过程 掌握MySQL触发器 1.视图: 视图就是一条select语句执行后返回的结果集. 使用场景:权限控制的时候不希望用户访问表中某些敏感信息的列 ...

  6. SpringCloud微服务:基于Nacos组件,整合Dubbo框架

    源码地址:GitHub·点这里 || GitEE·点这里 一.基础组件简介 1.Dubbo框架 Dubbo服务化治理的核心框架,之前几年在国内被广泛使用,后续由于微服务的架构的崛起,更多的公司转向微服 ...

  7. 咕咕咕清单(SCOI2020前)

    本篇博客已停更 本篇博客已停更 本篇博客已停更 吐槽区: 2020.04.15: 从今天起我做过的题目都记录一下,想不想写题解就另说了 2020.04.17: 写了两天之后真实的发现这是博主的摸鱼日记 ...

  8. Visual Studio安装

    2017 安装的时候,一直显示,安装成功但是有告警. 解决方法: 将visual studio 2017 installer进行卸载,然后安装hw的ios 不能确保下次也可以成功

  9. C#开发笔记之04-如何用C#优雅的计算个人所得税?

    C#开发笔记概述 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/960 访问. 首先,要对个人所得税的计算方式了解之后再 ...

  10. C#LeetCode刷题之#125-验证回文串(Valid Palindrome)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3899 访问. 给定一个字符串,验证它是否是回文串,只考虑字母和数 ...