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.pswp.cn/news/390360.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390360.shtml
英文地址,請注明出處:http://en.pswp.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,…