js之promise讲解
1 Promise概述
Promise对象是CommonJS工作组提出的一种规范,目的是为异步操作提供统一接口。
那么,什么是Promises?
首先,它是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样;其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介。它使得异步操作具备同步操作的接口,使得程序具备正常的同步运行的流程,回调函数不必再一层层嵌套。
简单说,它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用。
比如,异步操作f1返回一个Promise对象,它的回调函数f2写法如下。
(new Promise(f1)).then(f2);
这种写法对于多层嵌套的回调函数尤其方便。
// 传统写法
step1(function (value1) {
step2(value1, function(value2){
step3(value2,function(value3) {
step4(value3,function(value4) {
// ...
});
});
});
});
// Promises的写法
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
从上面代码可以看到,采用Promises接口以后,程序流程变得非常清楚,十分易读。
注意,为了便于理解,上面代码的Promise对象的生成格式,做了简化,真正的语法请参照下文。
总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promises规范就是为了解决这个问题而提出的,目标是使用正常的程序流程(同步),来处理异步操作。它先返回一个Promise对象,后面的操作以同步的方式,寄存在这个对象上面。等到异步操作有了结果,再执行前期寄放在它上面的其他操作。
Promises原本只是社区提出的一个构想,一些外部函数库率先实现了这个功能。ECMAScript 6将其写入语言标准,因此目前javascript语言原生支持Promise对象。
前面说过,Promise接口的基本思想是,异步任务返回一个Promise对象。
Promise对象只有三种状态。
• 异步操作“未完成”(pending)
• 异步操作“已完成”(resolved,又称fulfilled)
• 异步操作“失败”(rejected)
这三种的状态的变化途径只有两种。
• 异步操作从“未完成”到“已完成”
• 异步操作从“未完成”到“失败”。
这种变化只能发生一次,一旦当前状态变为“已完成”或“失败”,就意味着不会再有新的状态变化了。因此,Promise对象的最终结果只有两种。
• 异步操作成功,Promise对象传回一个值,状态变为resolved。
• 异步操作失败,Promise对象抛出一个错误,状态变为rejected。
Promise对象使用then方法添加回调函数。then方法可以接受两个回调函数,第一个是异步操作成功时(变为resolved状态)时的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(可以省略)。一旦状态改变,就调用相应的回调函数。
// po是一个Promise对象
po.then(
console.log,
console.error
);
上面代码中,Promise对象po使用then方法绑定两个回调函数:操作成功时的回调函数console.log,操作失败时的回调函数console.error(可以省略)。这两个函数都接受异步操作传回的值作为参数。
then方法可以链式使用。
po
.then(step1)
.then(step2)
.then(step3)
.then(
console.log,
console.error
);
上面代码中,po的状态一旦变为resolved,就依次调用后面每一个then指定的回调函数,每一步都必须等到前一步完成,才会执行。最后一个then方法的回调函数console.log和console.error,用法上有一点重要的区别。console.log只显示回调函数step3的返回值,而console.error可以显示step1、step2、step3之中任意一个发生的错误。也就是说,假定step1操作失败,抛出一个错误,这时step2和step3都不会再执行了(因为它们是操作成功的回调函数,而不是操作失败的回调函数)。Promises对象开始寻找,接下来第一个操作失败时的回调函数,在上面代码中是console.error。这就是说,Promises对象的错误有传递性。
从同步的角度看,上面的代码大致等同于下面的形式。
try {
var v1 = step1(po);
var v2 = step2(v1);
var v3 = step3(v2);
console.log(v3);
} catch (error) {
console.error(error);
}
ES6提供了原生的Promise构造函数,用来生成Promise实例。
下面代码创造了一个Promise实例。
var promise = new Promise(function(resolve, reject) {
// 异步操作的代码
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
po.then(function(value) {
// success
}, function(value) {
// failure
});
Promise的用法,简单说就是一句话:使用then方法添加回调函数。但是,不同的写法有一些细微的差别,请看下面四种写法,它们的差别在哪里?
// 写法一
doSomething().then(function () {
return doSomethingElse();
});
// 写法二
doSomething().then(function () {
doSomethingElse();
});
// 写法三
doSomething().then(doSomethingElse());
// 写法四
doSomething().then(doSomethingElse);
为了便于讲解,下面这四种写法都再用then方法接一个回调函数finalHandler。写法一的finalHandler回调函数的参数,是doSomethingElse函数的运行结果。
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
写法二的finalHandler回调函数的参数是undefined。
doSomething().then(function () {
doSomethingElse();
return;
}).then(finalHandler);
写法三的finalHandler回调函数的参数,是doSomethingElse函数返回的回调函数的运行结果。
doSomething().then(doSomethingElse())
.then(finalHandler);
写法四与写法一只有一个差别,那就是doSomethingElse会接收到doSomething()返回的结果。
doSomething().then(doSomethingElse)
.then(finalHandler);
2 promise.then
所有的 promise 对象实例里都有一个then 方法,它是用来跟这个 promise 进行交互的。首先,then 方法会缺省调用 resolve() 函数:
new Promise(function(resolve, reject) {
// Amock async action using setTimeout
setTimeout(function(){ resolve(10); }, 3000);
})
.then(function(result) {
console.log(result);
});
// From the console:
// 10
then 回调动作的触发时机是 promise 被执行完。我们还可以串联 then 方法执行回调操作:
new Promise(function(resolve, reject) {
// Amock async action using setTimeout
setTimeout(function(){ resolve(10); }, 3000);
})
.then(function(num) { console.log('first then:', num); return num * 2; })
.then(function(num) { console.log('second then:', num); return num * 2; })
.then(function(num) { console.log('last then: ',num);});
// From the console:
// first then: 10
// second then: 20
// lastthen: 40
你会发现,每次 then 调用都会以之前的 then 调用的返回值为参数。
如果一个 promise 已经执行完成,单then 被再次调用时,回调动作将会被再次执行。而如果这个 promise 里执行的是reject 回调函数,这是再调用 then 方法,回调函数将不会被执行。
3 promise.all
在我们的异步调用时经常有这样一种场景:我们需要同时调用多个异步操作,但希望只有等所有的操作都完成后,我们才去执行响应操作——这就是Promise.all 的作用。 Promise.all 方法可以接收多个 promise 作为参数,以数组的形式,当这些 promise 都成功执行完成后才调用回调函数。
Promise.all([promise1, promise2]).then(function(results){
//Both promises resolved
})
.catch(function(error) {
//One or more promises was rejected
});
一个很好的能演示 Promise.all 用法的例子是,执行多个 AJAX 操作(通过 fetch) 调用:
var request1 = fetch('/users.json');
var request2 = fetch('/articles.json');
Promise.all([request1, request2]).then(function(results) {
// Both promises done!
});
一旦promise 里调用了reject函数,也就是执行被拒绝了,没有能够正常完成,情况会有些复杂。一旦promise 被拒绝,catch 方法会捕捉到首个被执行的reject函数:
var req1 = new Promise(function(resolve, reject){
// Amock async action using setTimeout
setTimeout(function(){ resolve('First!'); }, 4000);
});
var req2 = new Promise(function(resolve, reject){
// Amock async action using setTimeout
setTimeout(function(){ reject('Second!'); }, 3000);
});
Promise.all([req1, req2]).then(function(results){
console.log('Then:', one);
}).catch(function(err) {
console.log('Catch:', err);
});
// From the console:
// Catch:Second!
4 promise.catch
catch 当一个 promise 被拒绝(reject)时,catch 方法会被执行:
new Promise(function(resolve,reject) {
// A mock async action using setTimeout
setTimeout(function() { reject('Done!'); },3000);
})
.then(function(e){ console.log('done', e); })
.catch(function(e){ console.log('catch: ', e); });
// From theconsole:
// 'catch:Done!'
通常我们在 reject 方法里处理执行失败的结果,而在catch 里执行异常结果:
reject(Error('Data could not be found'));
5 promise.race
Promise.race 是一个有趣的函数——它不是等待所有的 promise 被resolve 或 reject,而是在所有的 promise 中只要有一个执行结束,它就会触发:
var req1 = newPromise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() { resolve('First!'); },8000);
});
var req2 = newPromise(function(resolve, reject) {
// A mock async action using setTimeout
setTimeout(function() { resolve('Second!');}, 3000);
});
Promise.race([req1,req2]).then(function(one) {
console.log('Then: ', one);
}).catch(function(one,two) {
console.log('Catch: ', one);
});
// From theconsole:
// Then:Second!
一个有用的场景是,从多个镜像服务器下载资源,一旦有一个返回,其它的返回也就不用处理了。
6 promise实际应用
一个 XMLHttpRequest 调用转换为基于 Promises 的任务:
// From JakeArchibald's Promises and Back:
//http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest
function get(url){
// Return a new promise.
return new Promise(function(resolve, reject) {
// Do the usual XHR stuff
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
// This is called even on 404 etc
// so check the status
if (req.status == 200) {
// Resolve the promise with theresponse text
resolve(req.response);
}
else {
// Otherwise reject with the statustext
// which will hopefully be a meaningfulerror
reject(Error(req.statusText));
}
};
// Handle network errors
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send();
});
}
// Use it!
get('story.json').then(function(response){
console.log("Success!", response);
}, function(error){
console.error("Failed!", error);
});
Promise.resolve() 和 Promise.reject() 可以直接被调用。有时候,当判断出 promise 并不需要真正执行时,我们并不需要 使用 new 创建 Promise 对象,而是可以直接调用 Promise.resolve() 和 Promise.reject()。比如:
var userCache = {};
function getUserDetail(username) {
// In both cases, cached or not, apromise will be returned
if (userCache[username]) {
//Return a promise without the "new" keyword
return Promise.resolve(userCache[username]);
}
// Use the fetch API to get theinformation
// fetch returns a promise
return fetch('users/' + username + '.json')
.then(function(result) {
userCache[username] = result;
return result;
})
.catch(function() {
throw new Error('Could not finduser: ' + username);
});
}
因为 promise 肯定会返回,所以,我们可以使用then 和 catch 方法处理返回值!
加载图片
var preloadImage = function (path) {
return new Promise(function(resolve, reject) {
var image = newImage();
image.onload = resolve;
image.onerror= reject;
image.src = path;
});
};
js之promise讲解的更多相关文章
- js 深入原理讲解系列-Promise
js 深入原理讲解系列-Promise 能看懂这一题你就掌握了 js Promise 的核心原理 不要专业的术语,说人话,讲明白! Q: 输出下面 console.log 的正确的顺序? const ...
- js 深入原理讲解系列-事件循环
js 深入原理讲解系列-事件循环 能看懂这一题你就掌握了 js 事件循环的核心原理 不要专业的术语,说人话,讲明白! Q: 输出下面 console.log 的正确的顺序? console.log(' ...
- HTML5游戏开发引擎Pixi.js新手入门讲解
在线演示 本地下载 这篇文章中,介绍HTML5游戏引擎pixi.js的基本使用. 相关代码如下: Javascript 导入类库:(使用极客的cdn服务:http://cdn.gbtags.com) ...
- 关于学习js的Promise的心得体会
最近一直在研究js的Promise对象,其中有一篇blog写得比较通俗易懂,转发如下: http://www.cnblogs.com/lvdabao/p/es6-promise-1.html 参照上面 ...
- ES6---new Promise()讲解,Promise对象是用来干嘛的?
ES6---new Promise()讲解,Promise对象是用来干嘛的? :https://blog.csdn.net/Wbiokr/article/details/79490390
- js 深入原理讲解系列-currying function
js 深入原理讲解系列-currying function 能看懂这一题你就掌握了 js 科里函数的核心原理 不要专业的术语,说人话,讲明白! Q: 实现 sum 函数使得以下表达式的值正确 cons ...
- 讲解JS的promise,这篇是专业认真的!
http://www.zhangxinxu.com/wordpress/2014/02/es6-javascript-promise-%E6%84%9F%E6%80%A7%E8%AE%A4%E7%9F ...
- Angular JS中 Promise用法
一.Promise形象讲解A promise不是angular首创的,作为一种编程模式,它出现在1976年,比js还要古老得多.promise全称是 Futures and promises. 而在j ...
- js的promise
转载自: https://segmentfault.com/a/1190000007032448#articleHeader16 一 前言 本文主要对ES6的Promise进行一些入门级的介绍.要想学 ...
随机推荐
- CMDB服务器管理系统【s5day89】:采集资产之整合资产
1.业务逻辑单独写 1.代码目录结构 2.client.py from src.plugins import PluginManager class BaseClient(object): def p ...
- 深入剖析Kubernetes学习笔记:开篇词(00)
一.关于Kubernetes初学的疑惑 就在这场因"容器"而起的技术变革中,kubernetes项目已经成为容器技术的事实标准,重新定义了基础设置领域对应用编排与管理的种种可能 1 ...
- Java计算文件MD5值(支持大文件)
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.securit ...
- ArcGIS Server 10.0 安装及使用完整攻略
引言 ArcGIS Server 10.0在使用和安装的过程中,需要进行比较全面的学习,才能正确使用.缺乏正确的指引,用户很容易在安装及使用中遇到问题.所以笔者在此总结Server 10.0的安装及使 ...
- 携程apollo系列-个人开发环境搭建
本博客讲详细讲解如何在 Windows 搭建携程 Apollo 服务器 (用户个人开发). 开发服务器有多种搭建方式:(1) docker, 搭建过程非常简单, 推荐有 docker 环境(2) 部署 ...
- tex 进度条
\documentclass{beamer} \usepackage{tikz} \usetikzlibrary{calc} \definecolor{pbblue}{HTML}{0A75A8}% f ...
- 【*和&】复习-杂七杂八代码收集
int main() { ;//整数k,地址为x00CFFCA4 int &c = k;//引用参数c,c及&c的值与k及&k相同 int *w = &k;//指针 ...
- throws与throw
throws与throw 1)throws出现在方法函数头:而throw出现在函数体: 2)throws表示出现异常的一种可能性,并不一定会发生这些异常:throw则是抛出了异常,执行throw ...
- Linux配置日志服务器
title: Linux配置日志服务器 tags: linux, 日志服务器 --- Linux配置日志服务器 日志服务器配置文件:/etc/rsyslog.conf 服务器端: 服务器IP如下: 编 ...
- Android 控件背景选择图片还是drawable XML资源
决定一个控件应该是否用Drawable XML渲染,应考虑以下几个因素: * App是否要支持多分辨率: * App是否有瘦身的需要: * 图案是否足够简单: * 图案需要自由缩放: * 设计开发工作 ...