日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JS之Promise

發布時間:2023/11/29 javascript 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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(請求參數依賴上一次請求的結果)中經??梢猿霈F這種現象。 當然實際情況,我們不會那樣寫(什么?你就是這樣,趕緊改),而是會使用函數封裝一下再調用:

$.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庫即可,就可以得到一個全局的 Promise 和 P(別名) 對象。

<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]); 復制代碼

只有p1、p2、p3的狀態都變成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語句后面表達式的值,如果沒有,則是undefined。done屬性表示是否遍歷結束。

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

總結

以上是生活随笔為你收集整理的JS之Promise的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。