1. Promise简介

promise是异步编程的一种解决方案,它出现的初衷是为了解决回调地狱的问题。

打个比方,我需要:

--(延迟1s)--> 输出1 --(延迟2s)--> 输出2 --(延迟3s)--> 输出3

通常写法:

setTimeout(()=> {
console.log('1');
setTimeout(()=> {
console.log('2');
setTimeout(()=> {
console.log('3');
}, 3000)
}, 2000)
}, 1000)

这样的多重的嵌套的回调被称为回调地狱,这样的代码可读性很差,不利于理解。

如果用promise的话画风一转

function delay(time, num) {
return new Promise((res, rej)=> {
setTimeout(()=> {
console.log(num);
res();
}, time*1000)
});
}
delay(1, 1).then(()=> {
return delay(2, 2);
}).then(()=> {
delay(3, 3);
})
使用了promise的链式调用,代码结构更清晰。

是不是很棒?那还不赶快get起来~

2. Promise的使用

调用方式如下:

new Promise((resolve, reject)=> {
if('some option') {
resolve('some value');
} else {
reject('some error');
}
}).then(
val=> {
// ...
},
error=> {
// ...
}
)

Promise构造函数接收一个函数型参数fn,fn有两个参数,分别是:resolve、reject,Promise还有一个Promise.prototype.then方法,该方法接收两个参数,分别是成功的回调函数succ和失败的回调函数error。

在fn中调用resolve会触发then中的succ回调,调用reject会触发error回调。

2.1 参数传递

  • 在fn内部调用resolve/reject传入的参数会作为相应参数传入相应的回调函数

    new Promise((res, rej)=> {
    res('happy')
    }).then(val=> {
    console.log(val); // happy
    }); new Promise((res, rej)=> {
    rej('error!');
    }).then(val=> {}, err=> {
    console.log(err); // error!
    });
  • 链式调用时若上一级没有传递值则默认为undefined

    new Promise((res, rej)=> {
    res('a');
    }).then(val=> {
    return 'b'
    }).then(val=> {
    console.log(val); // 'b'
    }).then((val)=> {
    console.log(val); // 'undefined'
    });
  • 若上一级的then中传递的并非函数,则忽略该级

    new Promise((res, rej)=> {
    res('a');
    }).then(val=> {
    return 'b';
    }).then(val=> {
    console.log(val); // 'b'
    return 'c';
    }).then({ // 并非函数
    name: 'lan'
    }).then((val)=> {
    console.log(val); // 'c'
    });

2.2 参数传递例题

let doSomething = function() {
return new Promise((resolve, reject) => {
resolve('返回值');
});
}; let doSomethingElse = function() {
return '新的值';
} doSomething().then(function () {
return doSomethingElse();
}).then(resp => {
console.warn(resp);
console.warn('1 =========<');
}); doSomething().then(function () {
doSomethingElse();
}).then(resp => {
console.warn(resp);
console.warn('2 =========<');
}); doSomething().then(doSomethingElse()).then(resp => {
console.warn(resp);
console.warn('3 =========<');
}); doSomething().then(doSomethingElse).then(resp => {
console.warn(resp);
console.warn('4 =========<');
});

结合上面的讲解想一想会输出什么?(答案及解析

3. Promise.prototype.then

当Promise中的状态(pending ---> resolved or rejected)发生变化时才会执行then方法。

  • 调用then返回的依旧是一个Promise实例 ( 所以才可以链式调用... )
new Promise((res, rej)=> {
res('a');
}).then(val=> {
return 'b';
}); // 等同于
new Promise((res, rej)=> {
res('a');
}).then(val=> {
return new Promise((res, rej)=> {
res('b');
});
});
  • then中的回调总会异步执行
new Promise((res, rej)=> {
console.log('a');
res('');
}).then(()=> {
console.log('b');
});
console.log('c');
// a c b
  • 如果你不在Promise的参数函数中调用resolve或者reject那么then方法永远不会被触发
new Promise((res, rej)=> {
console.log('a');
}).then(()=> {
console.log('b');
});
console.log('c');
// a c

4. Promise的静态方法

Promise还有四个静态方法,分别是resolve、reject、all、race,下面我们一一介绍一下。

4.1 Promise.resolve()

除了通过new Promise()的方式,我们还有两种创建Promise对象的方法,Promise.resolve()相当于创建了一个立即resolve的对象。如下两段代码作用相同:

Promise.resolve('a');

new Promise((res, rej)=> {
res('a');
});

当然根据传入的参数不同,Promise.resolve()也会做出不同的操作。

  • 参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

  • 参数是一个thenable对象

thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};

Promise.resolve方法会将这个对象转为 Promise对象,然后就立即执行thenable对象的then方法。

  • 参数不是具有then方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

  • 不带有任何参数

Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

值得注意的一点是该静态方法是在本次事件轮询结束前调用,而不是在下一次事件轮询开始时调用。关于事件轮询可以看这里——>JavaScript 运行机制详解:再谈Event Loop

4.2 Promise.reject()

和Promise.resolve()类似,只不过一个是触发成功的回调,一个是触发失败的回调

4.3 Promise.all()

Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

function asyncFun1() {
return new Promise((res, rej)=> {
setTimeout(()=> {
res('a');
}, 1000);
});
}
function asyncFun2() {
return new Promise((res, rej)=> {
setTimeout(()=> {
res('b');
}, 1000);
});
}
function asyncFun3() {
return new Promise((res, rej)=> {
setTimeout(()=> {
res('c');
}, 1000);
});
}
Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
console.log(val);
});
Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
console.log(val); // ['a', 'b', 'c']
});

用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。

适用场景:打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

4.4 Promise.race()

race()和all相反,all()是数组中所有Promise都执行完毕就执行then,而race()是一旦有一个Promise执行完毕就会执行then(),用上面的三个Promise返回值函数举例

Promise.race([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
console.log(val); // a
});

5. 链式调用经典例题

看了这么多关于Promise的知识,我们来做一道题巩固一下。

写一个类Man实现以下链式调用

调用方式:
new Man('lan').sleep().eat('apple').sleep().eat('banana');
打印:
'hello, lan' -(等待3s)--> 'lan eat apple' -(等待5s)--> 'lan eat banana'

思路:

  • 在原型方法中返回this达到链式调用的目的
  • 等待3s执行的效果可以用Promise & then实现

具体实现如下:

class Man {
constructor(name) {
this.name = name;
this.sayName();
this.rope = Promise.resolve(); // 定义全局Promise作链式调用
}
sayName() {
console.log(`hello, ${this.name}`);
}
sleep(time) {
this.rope = this.rope.then(()=> {
return new Promise((res, rej)=> {
setTimeout(()=> {
res();
}, time*1000);
});
});
return this;
}
eat(food) {
this.rope = this.rope.then(()=> {
console.log(`${this.name} eat ${food}`);
}); return this;
}
} new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');

ok!不知道你有没有看懂呢?如果能完全理解代码那你的Promise可以通关了,顺便来个小思考,下面这种写法可以吗?和上面相比有什么区别?:

class Man1345 {
constructor(name) {
this.name = name;
this.sayName();
}
sayName() {
console.log(`hello, ${this.name}`);
}
sleep(time) {
this.rope = new Promise((res, rej)=> {
setTimeout(()=> {
res();
}, time*1000);
});
return this;
}
eat(food) {
this.rope = this.rope.then(()=> {
console.log(`${this.name} eat ${food}`);
}); return this;
}
} new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');

简单的说,第二段代码的执行结果是

'hello, lan' -(等待3s)--> 'lan eat apple' ---> 'lan eat banana'

为什么会出现这种差别? 因为第二段代码每一次调用sleep都会new一个新的Promise对象,调用了两次sleep就new了两个Promise对象。这两个对象是异步并行执行,会造成两句eat同时显示。

和以下情况类似

var time1 = setTimeout(()=> {
console.log('a');
}, )
var time2 = setTimeout(()=> {
console.log('b');
}, )
// 同时输出 a b

抽象一点的讲解是:

// 第一段正确的代码的执行为
var p1 = new Promise().then('停顿3s').then('打印食物').then('停顿5s').then('打印食物'); // 第二段代码的执行行为,p1、p2异步并行执行
var p1 = new Promise().then('停顿3s').then('打印食物');
var p2 = new Promise().then('停顿5s').then('打印食物');
总结

Promise的经常用到的地方:

  • 摆脱回调地狱
  • 多个异步任务同步

Promise是我们的好帮手,不过还有另一种方法也可以做到,那就是async&await,可以多多了解一下。

参考资料

ECMAScript 6 入门

通俗浅显的理解Promise中的then

大白话讲解promise

原来你是这样的Promise的更多相关文章

  1. Javascript - Promise学习笔记

    最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下.   一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...

  2. 路由的Resolve机制(需要了解promise)

    angular的resovle机制,实际上是应用了promise,在进入特定的路由之前给我们一个做预处理的机会 1.在进入这个路由之前先懒加载对应的 .js $stateProvider .state ...

  3. angular2系列教程(七)Injectable、Promise、Interface、使用服务

    今天我们要讲的ng2的service这个概念,和ng1一样,service通常用于发送http请求,但其实你可以在里面封装任何你想封装的方法,有时候控制器之间的通讯也是依靠service来完成的,让我 ...

  4. 闲话Promise机制

    Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval.DOM事件机制.ajax,通过传入回调函数实现控制反转.异步编程为js ...

  5. 深入理解jQuery、Angular、node中的Promise

    最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...

  6. Promise的前世今生和妙用技巧

    浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一 ...

  7. JavaScript进阶之路——认识和使用Promise,重构你的Js代码

    一转眼,这2015年上半年就过去了,差不多一个月没有写博客了,"罪过罪过"啊~~.进入了七月份,也就意味着我们上半年苦逼的单身生活结束了,从此刻起,我们要打起十二分的精神,开始下半 ...

  8. 细说Promise

    一.前言 JavaScript是单线程的,固,一次只能执行一个任务,当有一个任务耗时很长时,后面的任务就必须等待.那么,有什么办法,可以解决这类问题呢?(抛开WebWorker不谈),那就是让代码异步 ...

  9. 浅谈Angular的 $q, defer, promise

    浅谈Angular的 $q, defer, promise 时间 2016-01-13 00:28:00  博客园-原创精华区 原文  http://www.cnblogs.com/big-snow/ ...

  10. angular学习笔记(二十八-附2)-$http,$resource中的promise对象

    下面这种promise的用法,我从第一篇$http笔记到$resource笔记中,一直都有用到: HttpREST.factory('cardResource',function($resource) ...

随机推荐

  1. C# Int 类型线程不安全

    之前统计报表算法做了一个优化,一个查询二十分钟导致客户端超时,优化到只需要5秒钟.后来发现for循环里数据合并的时候耗时,就用并行做优化.但是发现并行后丢居然数据(当然是因为List线程不安全). 前 ...

  2. redis客户端可以连接集群,但JedisCluster连接redis集群一直报Could not get a resource from the pool

    一,问题描述: (如题目)通过jedis连接redis单机成功,使用JedisCluster连接redis集群一直报Could not get a resource from the pool 但是使 ...

  3. 【Python】 文件目录比较工具filecmp和difflib

    在一些运维场景中,常常需要比较两个环境中的应用目录结构(是否有文件/目录层面上的增删)以及比较两个环境中同名文件内容的不同(即文件层面上的改).Python自带了两个内建模块可以很好地完成这个工作,f ...

  4. 不可思议的纯CSS导航栏下划线跟随效果

    先上张图,如何使用纯 CSS 制作如下效果? 在继续阅读下文之前,你可以先缓一缓.尝试思考一下上面的效果或者动手尝试一下,不借助 JS ,能否巧妙的实现上述效果. OK,继续.这个效果是我在业务开发的 ...

  5. 《HelloGitHub》第 24 期(两周年)

    公告 今天是<HelloGitHub>月刊 两周年.当时发布第一期的时候,根本没有想到可以走到现在. 这两年,HelloGitHub 项目有过辉煌的时刻:连续 3 天 GitHub 趋势首 ...

  6. Linux安装java环境教程

    前言: 本教程基于jdk 1.8,但是此教程适用于jdk1.7等版本. 教程正文: 1.1. 登录Oracle官网下载jdk1.8安装包(gz结尾) 这里可以用"wget + 下载地址&qu ...

  7. Java基础学习笔记十六 集合框架(二)

    List List接口的特点: 它是一个元素存取有序的集合.例如,存元素的顺序是11.22.33.那么集合中,元素的存储就是按照11.22.33的顺序完成的. 它是一个带有索引的集合,通过索引就可以精 ...

  8. Hibernate之深入持久化对象

    Hibernate是一个彻底的O/R Mapping 框架.之所以说彻底,是因为相对于其他的 框架 ,如Spring JDBC,iBatis 需要手动的管理SQL语句,Hibernate采用了完全 面 ...

  9. 2017北京国庆刷题Day1 afternoon

    期望得分:100+100+100=300 实际得分:100+100+100=300 T1 一道图论好题(graph) Time Limit:1000ms   Memory Limit:128MB 题目 ...

  10. 使用HttpClient4.5实现HTTPS的双向认证

    说明:本文主要是在平时接口对接开发中遇到的为保证传输安全的情况特要求使用https进行交互的情况下,使用httpClient4.5版本对HTTPS的双向验证的  功能的实现    首先,老生常谈,文章 ...