JS之Promise

开胃菜,先做如下思考:

  • Promise 有几种状态?
  • Promise 状态之间可以转化吗?
  • Promise 中的异常可以被 try...catch 捕获吗?

Promise前世

callback hell

大家都知道JS是异步操作(执行)的,在传统的异步编程中最大的问题就是回调函数的嵌套,一旦嵌套次数过多,我们的代码就难以维护和理解,这就是所谓的 回调地狱callback hell 。以jQuery为例:

$.ajax({url: "/getA",success: function(a) {$.ajax({url: '/getB',data: a.data,success: function(b){$.ajax({url: '/getC',data: b.data,success: function(c){console.log('运行到这真不容易')}})}});}
});
复制代码

特别是在ajax(请求参数依赖上一次请求的结果)中经常可以出现这种现象。 当然实际情况,我们不会那样写(什么?你就是这样,赶紧改),而是会使用函数封装一下再调用:

$.ajax({url: "/getA",success: function(a) {getB(a.data)}
});getB(data){$.ajax({url: '/getB',data: data,success: function(b){getC(b.data)}})
}getC(data){$.ajax({url: '/getC',data: data,success: function(c){console.log('运行到这真不容易')}})
}
复制代码

but,这还是回调函数调用,只不过换了种便于维护的另一种写法。

除非是老项目不想进行改动,新项目中最好不要这么干了

jQuery.defered

为了解决上述情况,jQuery在v1.5 版本中引入了 deferred 对象,初步形成 promise 概念。ajax 也成了一个 deferred 对象。

$.ajax({url: "/getA",
}).then(function(){console.log('a')
});
复制代码

我们也可以这样来定义一个 deferred 对象:

function loadImg(src){var dtd = $.Deferred();  // 定义延迟对象var img = new Image();img.onload = function(){dtd.resolve(img)}img.onerror = function(){dtd.reject()}img.src = src;return dtd;  // 记得要返回哦
}
var result = loadImg('img.png');
result.then(function(img){console.log(img.width)
},function(){console.log('fail')
})
复制代码

完整例子:戳我

在 ES6 Promise 出现之前,有很多典型的Promise库,如:bluebird、Q 、when 等。

bluebird

bluebird是一个第三方Promise规范实现库,它不仅完全兼容原生Promise对象,且比原生对象功能更强大。

安装

Node:

npm install bluebird
复制代码

Then:

const Promise = require("bluebird");
复制代码

Browser: 直接引入js库即可,就可以得到一个全局的 PromiseP(别名) 对象。

<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>
复制代码

使用

function loadImg(src) {return new Promise((resolve,reject) => {const img = new Image();img.onload = ()=>{resolve(img);}img.onerror = () => {reject()}img.src = src;})
}
const result = loadImg('http://file.ituring.com.cn/SmallCover/17114893a523520c7382');
result.then(img => {console.log(img.width)
},() => {console.log('fail')
});
console.log(Promise === P)  // true
复制代码

为了与原生的 Promise 进行区别,我们在控制台中打印了 Promise === P 的结果。结果和预期一样:

完整demo:戳我

bluebird 相比原生规范实现来说,它的功能更强大,浏览器兼容性好(IE8没问题),提供了很多丰富的方法和属性:

  • Promise.props
  • Promise.any
  • Promise.some
  • Promise.map
  • Promise.reduce
  • Promise.filter
  • Promise.each
  • Promise.mapSeries
  • cancel
  • ...more

更多详细的功能查看 官网API 。

我们发现,这里的 Promise 是可以取消的。

Promise今生

Promise最早是由社区提出和实现的,ES6写成了语言标准,它给我们提供一个原生的构造函数Promise,无需使用第三方库或者造轮子来实现。

Promise 语法

function loadImg(src) {return new Promise((resolve,reject) => {const img = new Image();img.onload = ()=>{resolve(img);}img.onerror = () => {reject()}img.src = src;})
}
const result = loadImg('http://file.ituring.com.cn/SmallCover/17114893a523520c7382');
result.then(img => {console.log(img.width)
},() => {console.log('fail')
})
复制代码

Promise 对象代表一个异步操作,有三种状态:pending (进行中)、fulfilled(已成功)和 rejected (已失败)。在代码中,经常使用 resolve 来表示 fulfilled 状态。

Promise 特点

  • 状态的不可改变,状态一旦由 pending 变为 fulfilled 或从 pending 变为 rejected ,就不能再被改变了。
  • Promise无法取消,一旦新建它就会立即执行,无法中途取消,对于 ajax 类的请求无法取消,可能存在资源浪费情况。
  • Promise内部抛出的错误,不会反应到外部,无法被外部 try...catch 捕获,只能设置 catch 回调函数或者 then(null, reject) 回调
try{new Promise((resolve,reject)=>{throw new Error('错误了')}).catch(()=>{console.log('只能被catch回调捕获')})
}catch(e){console.log('只怕永远到不了这里啦')
}
复制代码

方法

  • Promise.prototype.then() then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。 then 会创建并返回一个新的promise,可以用来实现Promise 链式操作。

思考:Promise.then 链式和 jQuery的链式操作有何不同? jQuery的链式方法返回的都是jQuery当前对象

  • Promise.prototype.catch() .then(null, rejection)的别名,用于指定发生错误时或者状态变成 rejected的回调函数。

  • Promise.prototype.finally() ES 2018引入的标准,不管 promise 的状态如何,只要完成,都会调用该函数。

  • Promise.all() 将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);
复制代码

只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,否则p的状态就变成rejected。 这种模式在传统上称为__门__:所有人到齐了才开门。 适用场景:需要等待多个并行任务完成之后才能继续下一个任务。 典型例子:一个页面有多个请求,在请求完成或失败前需要一直显示loading效果。

  • Promise.race() 和 Promise.all 一样,将多个 Promise 实例,包装成一个新的 Promise 实例。不同的是,只要p1p2p3之中有一个实例率先改变状态( fulfilled或者rejected),p的状态就跟着改变。 这种模式传统上称为__门闩__:第一个到达的人就打开门闩。 典型例子:超时检测

  • Promise.resolve() 将现有对象转为 Promise 对象,Promise.resolve等价于:

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码
  • Promise.reject() 将现有对象转为 Promise 对象,Promise.reject等价于:
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
复制代码

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,Generator 函数被调用后并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(Iterator对象)。

与普通函数相比,它有两个特征:

  • function关键字与函数名之间有一个星号;
  • 函数体内部使用yield表达式

ES6 没有规定 function 关键字与函数名之间星号的位置,下面写法都能通过:

function * foo() { ··· }
function *foo() { ··· }
function* foo() { ··· }
function*foo() { ··· }
复制代码
function *helloWorldGen() {yield 'hello';yield 'world';return 'ending';
}const hw = helloWorldGen();
复制代码

定义之后,需要调用遍历器对象的next方法。每次调用next方法,从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。next方法返回一个对象,包含valuedone属性,value属性是当前yield表达式值或者return语句后面表达式的值,如果没有,则是undefineddone属性表示是否遍历结束。

hw.next()
// { value: 'hello', done: false }hw.next()
// { value: 'world', done: false }hw.next()
// { value: 'ending', done: true }hw.next()
// { value: undefined, done: true }
复制代码

yield

yield 表达式就是暂停标志,只能用在 Generator 函数里。 Generator 函数可以不使用 yield 表达式,这样就变成一个单纯的暂缓执行函数。

function *slow(){console.log('调用next才执行呀')
}
const gen = slow();
gen.next();
复制代码

yield 可以接受 next 方法的参数作为上一个 yield 表达式的返回值。

function *foo(x) {var y = x * (yield);return y;
}
const it = foo(6);
it.next(); // 启动foo()
// {value: undefined, done: false}it.next(7)
// {value: 42, done: true}
复制代码

第一次使用next方法时,传递参数是无效的。

for...of循环

使用for...of循环,可以自动遍历 Generator 函数生成的迭代器对象,此时不需要调用 next 方法。

function *foo() {yield 1;yield 2;yield 3;yield 4;yield 5;return 6;
}
// 到6时done:true,不会进入循环体内执行
for (let v of foo()) {console.log(v);
}
// 1 2 3 4 5
复制代码

for...of 循环在每次迭代中自动调用 next() ,不会给 next 传值,并且在接收到 done:true 之后自动停止(不包含此时返回的对象)。

对于一些迭代器总是返回 done:false 的,需要加一个 break 条件,防止死循环。

我们也可以手动实现迭代器循环:

let it = foo();
// 这种的有点就是可以向next传递参数
for(let ret; (ret=it.next()) && !ret.done;) {console.log(ret.value)
}
复制代码

Generator + Promise

我们先看下基于Promise 的实现方法:

function getData() {return request('http://xxx.com')
}
getData().then((res)=> {console.log(res)},()=>{console.log('fail')})
复制代码

结合Generator使用:

function getData() {return request('http://xxx.com')
}
function *main(){try {const text = yield getData();console.log(text)} catch (error) {console.log(error)}
}
复制代码

执行main方法如下:

const it = main();
const p = it.next().value;
p.then((text)=>{console.log(text)
},(err)=>{console.log(err)
})
复制代码

尽管 Generator 函数将异步操作简化,但是执行的流程管理很不方便(需要手动调用 next 执行),有更好的方式吗?肯定是有的。

co

co 是TJ 大神发布的一个 Generator 函数包装工具,用于自动执行 Generator 函数。

co 模块可以让我们不用编写 next 进行迭代,就会自动执行:

const co = require('co');function getData() {return request('http://xxx.com')
}
const gen = function *(){const text = yield getData();console.log(text)
}co(gen).then(()=>{console.log('gen执行完成')
})
复制代码

co函数返回一个Promise对象,等到 Generator 函数执行结束,就会输出一行提示。

async & await

ES2017 标准引入了 async 函数,它就是 Generator 函数的语法糖。

Node V7.6+已经原生支持 async 了。 Koa2 也使用 async 替代之前的 Generator 版本。

基本用法

async function fetchImg(url) {const realUrl = await getMainUrl(url);const result = await downloadImg(realUrl);return result;
}fetchImg('https://detail.1688.com/offer/555302162390.html').then((result) => {console.log(result)
})
复制代码

和 Generator 函数对比,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await。其功能和 co 类似,自动执行。

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。 async函数内部return语句返回的值,会成为then方法回调函数的参数。 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

await后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。

async function f() {return await 123;
}f().then(v => console.log(v))
// 123
复制代码

await 必须在 async 函数中执行!

实例:按顺序完成异步操作

讲一下一个可能会遇到的场景:经常遇到一组异步操作,需要按照顺序完成。比如,依次根据图片url下载图片,按照读取的顺序输出结果。

一个async的实现:

async function downInOrder(urls, path, win) {for(const url of urls) {try {await downloadImg(url, path)win.send('logger', `图片 ${url} 下载完成`)} catch (error) {win.send('logger', `图片 ${url} 下载出错`)}}
}
复制代码

上述这种实现,代码确实简化了,但是效率很差,需要一个操作完成,才能进行下一个操作(下载图片),不能并发执行。

并发执行,摘自我的一个半成品1688pic :

async function downInOrder(urls, path, win) {// 并发执行const imgPromises = urls.map(async url => {try {const resp = await downloadImg(url, path);return `图片 ${url} 下载完成`;} catch (error) {return `图片 ${url} 下载出错`;}})// 按顺序输出for (const imgPromise of imgPromises) {win.send('logger', await imgPromise);}
}
复制代码

上面代码中,虽然map方法的参数是async函数,但它是并发执行的,因为只有async函数内部是继发执行,外部不受影响。后面的for..of循环内部使用了await,因此实现了按顺序输出。

这块基本参考的是阮一峰老师的教程

总结

使用 async / await, 可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性。

参考文档:

  • es6入门
  • 你不知道的JavaScript(中卷)

转载于:https://juejin.im/post/5b0ea519518825154b147924

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/390360.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鱼眼镜头的distortion校正【matlab】

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 作者&#xff1a;WWC %%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% 功能&#xff1a;畸变矫正 clc; clear; close all; %% 读取图像 Aimread(D:\文件及下载相关\图片\distortion2.jpg)…

web后端开发学习路线_学习后端Web开发的最佳方法

web后端开发学习路线My previous article described how you can get into frontend development. It also discussed how the front end can be a place filled with landmines – step in the wrong place and youll be overwhelmed by the many frameworks of the JavaScrip…

C# 使用WinApi操作剪切板Clipboard

前言&#xff1a; 最近正好写一个程序&#xff0c;需要操作剪切板 功能很简单&#xff0c;只需要从剪切板内读取字符串&#xff0c;然后清空剪切板&#xff0c;然后再把字符串导入剪切板 我想当然的使用我最拿手的C#来完成这项工作&#xff0c;原因无他&#xff0c;因为.Net框架…

聊聊spring cloud gateway的XForwardedHeadersFilter

序 本文主要研究spring cloud gateway的XForwardedHeadersFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC1-sources.jar!/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java Configuration ConditionalOnProperty(name "sp…

node缓冲区_Node.js缓冲区介绍

node缓冲区什么是缓冲液&#xff1f; (What are Buffers?) Binary is simply a set or a collection of 1 and 0. Each number in a binary, each 1 and 0 in a set are called a bit. Computer converts the data to this binary format to store and perform operations. Fo…

专访赵加雨:WebRTC在网易云信的落地

去年的这个时候&#xff0c;在市面上公开表示使用WebRTC的公司还没几家&#xff0c;但2018年以来&#xff0c;宣布采用或支持WebRTC的公司已经越来越多。实时音视频提供商网易云信也在自研的NRTC中集成了WebRTC。在他们眼里&#xff0c;2017年是WebRTC的转折之年&#xff0c;而…

html/css杂题

1、css选择器&#xff1a;详细&#xff08;http://www.ruanyifeng.com/blog/2009/03/css_selectors.html&#xff09; 派生选择器&#xff1a;按标签 类别选择器&#xff1a;按class ID选择器&#xff1a;按ID 通用选择器&#xff1a;* 匹配所有 属性选择器&#xff1a;按属性&…

黑客马拉松 招募_我如何赢得第一次黑客马拉松-研究,设计和编码的2个狂野日子

黑客马拉松 招募I had no coding or engineering background. I studied biology in college, with no clue about what to do with my degree. 我没有编码或工程背景。 我在大学学习生物学&#xff0c;但不知道如何处理我的学位。 My first jobs were making cold calls in s…

1、Linux命令随笔

1 Linux命令总结2 3 man 命令帮助;4 help 命令的帮助&#xff08;bash的内置命令&#xff09;;5 ls list,查看目录列表;6 -ld&#xff1a;查看目录权限;7 -l:(long)长格式显示属性;8 -F:给不同的文件类型结尾加标识9 -p:给目录加斜线10 …

1137. 第 N 个泰波那契数

泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波那契数 Tn 的值。 示例 1&#xff1a; 输入&#xff1a;n 4 输出&#xff1a;4 解释&#xff1a; T_3 0 1 1 2 T_4 1…

web图像_Web图像优化的基本介绍

web图像Images are an essential ingredient of most websites. The visual quality of pictures has a direct impact on the brand image and the message those images convey. And the weight of images usually accounts for a 40-60% of the data transferred on the web…

ElasticSearch客户端注解使用介绍

The best elasticsearch highlevel java rest api-----bboss 1.ElasticSearch客户端bboss提供了一系列注解 ESId 用于标识实体对象中作为docid的属性&#xff0c;该注解只有一个persistent 布尔值属性&#xff0c;用于控制被本注解标注的字段属性是否作为普通文档属性保存&am…

5827. 检查操作是否合法

给你一个下标从 0 开始的 8 x 8 网格 board &#xff0c;其中 board[r][c] 表示游戏棋盘上的格子 (r, c) 。棋盘上空格用 ‘.’ 表示&#xff0c;白色格子用 ‘W’ 表示&#xff0c;黑色格子用 ‘B’ 表示。 游戏中每次操作步骤为&#xff1a;选择一个空格子&#xff0c;将它变…

团队的远程管理_远程团队指南:如何管理您的远程软件开发团队

团队的远程管理Guides to help you work remotely seem to have swept through the Internet these days. 这些天来&#xff0c;帮助您远程工作的指南似乎席卷了Internet。 Do this, avoid that, stay productive, and all those run-of-the-mill tips we’ve already tried o…

JS 正则 钱

function ValidateIsDecial(sValue) {return (!sValue && !!!sValue && /^[0-9]{1,10}(\.[0-9]{0,2})?$/.test(sValue)); };验证 decimal(12,2) 小数点前允许10位,小数点后允许2位 1234567890 true 12345678901 false 0123456789 true 01234567891 false 123.…

5193. 删除字符使字符串变好

5193. 删除字符使字符串变好 一个字符串如果没有 三个连续 相同字符&#xff0c;那么它就是一个 好字符串 。 给你一个字符串 s &#xff0c;请你从 s 删除 最少 的字符&#xff0c;使它变成一个 好字符串 。 请你返回删除后的字符串。题目数据保证答案总是 唯一的 。 示例 …

2020计算机顶级大会_2020年顶级远程调试工具

2020计算机顶级大会When it comes to debugging, the tool you use is extremely important and can determine how easy is is to fix problems within your code. 在调试方面&#xff0c;您使用的工具非常重要&#xff0c;可以确定在代码中修复问题的难易程度。 In the earl…

BZOJ5292 洛谷4457 LOJ2513:[BJOI2018]治疗之雨——题解

https://www.lydsy.com/JudgeOnline/problem.php?id5292 https://www.luogu.org/problemnew/show/P4457 https://loj.ac/problem/2513 你现在有m1个数&#xff1a;第一个为p&#xff0c;最小值为0&#xff0c;最大值为n&#xff1b;剩下m个都是无穷&#xff0c;没有最小值或最…

PHP--------微信网页开发实现微信扫码功能

今天说说微商城项目中用到的扫一扫这个功能&#xff0c;分享一下&#xff0c;希望对各位有所帮助。 前提&#xff1a;要有公众号&#xff0c;和通过微信认证&#xff0c;绑定域名&#xff0c;得到相应信息&#xff0c;appid&#xff0c;appsecret等。 微信开发文档&#xff1a;…

313. 超级丑数

超级丑数 是一个正整数&#xff0c;并满足其所有质因数都出现在质数数组 primes 中。 给你一个整数 n 和一个整数数组 primes &#xff0c;返回第 n 个 超级丑数 。 题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。 示例 1&#xff1a; 输入&#xff1a;n 12,…