【10】ES6: Promise object

1. Synchronous and asynchronous

1. JS is a single-threaded language

JavaScript 是一门单线程的语言, so only one thing can be done at the same time, which means that all tasks need to be queued, and the next task will not be executed until the previous task is executed. However, if the execution time of the previous task is very long, the latter task will need to wait forever, which will cause the rendering of the page to be inconsistent and seriously affect the user experience.

Therefore, JavaScript has already taken this problem into consideration when designing it. The main thread does not need to wait for the completion of these time-consuming tasks (these time-consuming tasks are being executed at this time). It runs the tasks at the back first and waits until these time-consuming tasks are completed. After having the results, go back and execute them. Therefore, all tasks can be divided into two types, one is synchronous task and the other is asynchronous task. (Summary: Because it is single-threaded, it must be asynchronous.)

2. Synchronous tasks and asynchronous tasks

同步任务It means that for tasks queued for execution on the main thread, the next task can only be executed after the previous task has been executed.

异步任务It refers to tasks that do not enter the main thread but enter the "task queue". Only when the "task queue" notifies the main thread that an asynchronous task can be executed will the task enter the main thread for execution.

The difference between them is the order of execution on this main thread. After all synchronous tasks are executed, asynchronous tasks will be executed. If a synchronous task is not executed, the program will be stuck there, and subsequent tasks cannot be executed. When an asynchronous task is encountered, the program will not be stuck, but will continue. Perform subsequent tasks.

Summary: If the synchronous task has not been executed, the asynchronous task will not be executed.

3. Event loop mechanism

JS runs in a single thread, and asynchronous is implemented based on callback functions. The event loop mechanism (Event Loop) is the principle of asynchronous callback implementation.

JavaScript running mechanism, alias event loop (EventLoop)

  • All synchronization tasks are executed on the main thread and form an execution stack.
  • In addition to the main thread, there is also a task queue. As long as the asynchronous task has a result, the corresponding task (callback function) will be placed in the task queue, waiting for execution.
  • Once all synchronization tasks in the execution stack are executed, the JS engine will determine whether there are tasks in the task queue that can be executed. If so, the task will end the waiting state, enter the execution stack, and start execution.
  • The main thread continuously repeats the third step above.

Insert image description here
Summary : As long as the main thread is empty, it will read the "task queue". This process is a continuous loop. This is the asynchronous execution mechanism of JavaScript, also called the event loop.

important point:

Asynchronous tasks and callback functions of asynchronous tasks are different. The task queue stores the callback functions of asynchronous tasks.

The time to put it into the task queue is not to put it in as soon as an asynchronous task is encountered. If an asynchronous task is encountered, it will be placed in the host environment. If the asynchronous task in the host environment reaches the conditions for execution, the callback function to be executed is placed in the task queue. For example, if a 1s timer setTimeout is encountered, the callback function will be placed in the task queue after 1s. middle.

The stack is a first-in, first-out data structure. The tasks at the bottom of the stack are executed the slowest. The queue is a first-in, first-out data structure. The tasks that go to the queue first are executed first.

4. Microtasks and macrotasks

Asynchronous tasks are divided into microtasks and macrotasks, that is, the task queue is divided into microtask queues and macrotask queues.

The triggering timing of microtasks is earlier than that of macrotasks, because microtasks are executed before DOM rendering, and macrotasks are executed after DOM rendering.

Microtasks: Promise, async, await, nextTick, etc.

Macro tasks: setTimeout, setInterval, ajax, DOM events, etc.

2. Basic understanding of Promise

1. The meaning of Promise

Promise is a solution to asynchronous programming that is more reasonable and powerful than traditional solutions (callback functions and events). It was first proposed and implemented by the community. ES6 wrote it into the language standard, unified its usage, and provided Promise objects natively.

The so-called Promise is simply a container that stores the result of an event (usually an asynchronous operation) that will end in the future. Syntactically speaking, Promise is an object from which messages for asynchronous operations can be obtained. Promise provides a unified API, and various asynchronous operations can be processed in the same way.

Characteristics of Promise objects:

(1)对象的状态不受外界影响

The Promise object represents an asynchronous operation and has three states: waiting state (pending) , success state (resolved) , and failure state (rejected) . Only the result of the asynchronous operation can determine the current state, and no other operation can change this state. This is also the origin of the name Promise. Its English meaning is "commitment", which means that it cannot be changed by other means.

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果

There are only two possibilities for the state of a Promise object to change: from pending to fulfilled and from pending to rejected. As long as these two situations occur, the state will be solidified, will not change anymore, and will maintain this result. This is called resolved. If the change has already occurred, if you add a callback function to the Promise object, you will get the result immediately. This is completely different from an event. The characteristic of an event is that if you miss it and listen again, you will not get the result.

Advantages of Promise objects:

(1) It realizes the expression of asynchronous operations in the process of synchronous operations and avoids nested callback functions. (callback hell)

(2) Provide a unified interface to make it easier to control asynchronous operations.

Disadvantages of Promise objects:

(1) Promise cannot be canceled. Once it is created, it will be executed immediately and cannot be canceled midway.

(2) If the callback function is not set, errors thrown internally by Promise will not be reflected externally.

(3) When in the pending state, it is impossible to know which stage of the current progress (has just started or is about to be completed).

2. Basic usage

ES6 stipulates that the Promise object is a constructor used to generate Promise instances.

The Promise constructor accepts a function as a parameter, and the two parameters of the function are resolve and reject. They are two functions provided by the JavaScript engine and do not need to be deployed by yourself.

parameter illustrate
resolve function Change the state of the Promise object from waiting state -> success state;
call it when the asynchronous operation is successful, and pass the result of the asynchronous operation as a parameter;
it will trigger the subsequent then callback function.
reject function Change the state of the Promise object from the waiting state to the failed state;
call it when the asynchronous operation fails, and pass the error reported by the asynchronous operation as a parameter; the
catch callback function will be triggered.
// 这里的 resolve reject 只是一个形参,可以取任意名字,但是我们约定直接使用 resolve reject 。
const promise = new Promise(function(resolve, reject) {
    
    
	// ... some code
	if (/* 异步操作成功 */){
    
    
		resolve(value)
	} else {
    
    
		reject(error)
	}
})

Promise will be executed immediately after it is created.

let promise = new Promise(function(resolve, reject) {
    
    
	console.log('Promise')
	resolve()
})

promise.then(function() {
    
    
	console.log('resolved.')
})

console.log('Hi!')

// Promise
// Hi!
// resolved

In the above code, the Promise is executed immediately after it is created, so the Promise is output first. Then, the callback function specified by the then method will not be executed until all synchronization tasks of the current script have been executed, so resolved is output at the end.

Calling resolve or reject does not end the execution of the Promise's parameter function.

new Promise((resolve, reject) => {
    
    
	resolve(1)
	console.log(2)
}).then(r => {
    
    
	console.log(r)
})
// 2
// 1

In the above code, after calling resolve(1), the subsequent console.log(2) will still be executed and will be printed out first. This is because the immediately resolved Promise is executed at the end of this round of event loop, always later than the synchronization task of this round of loop.

Generally speaking, after calling resolve or reject, the mission of the Promise is completed. Subsequent operations should be placed in the then method instead of written directly after resolve or reject. Therefore, it is best to add a return statement before them so that there are no surprises.

new Promise((resolve, reject) => {
    
    
	return resolve(1)
	// 后面的语句不会执行
	console.log(2)
})

三、Promise.prototype.then()

Promise instances have a then method, that is, the then method is defined on the prototype object Promise.prototype. Its function is to add a callback function to the Promise instance when its state changes.

The first parameter of the then method is the callback function of the resolved state, and the second parameter is the callback function of the rejected state. They are both optional.

1. When will the two callback functions of the then method be executed.

When pending -> resolved, execute the first callback function of then;

When pending -> rejected, execute the second callback function of then.

2. The return value after the then method is executed.

The then method automatically returns a new Promise object by default after execution.

3. The state of the Promise object returned by the then method changes.

The then method actually returns undefined by default, that is: return undefined, but the ES6 mechanism stipulates that when then returns undefined, the undefined will be packaged into a Promise, and this Promise will call the resolve() method by default (successful state) , and use undefined as a parameter of resolve(), which is equivalent to:

const p = new Promise((resolve, reject) => {
	resolve()
})
p.then(() => {
	// 默认会执行这一条
	// return undefined
}, () => {
})

// 实际上,return 会包装为一个 Promise 对象,同时默认执行 resolve(),并把 return 的值作为 resolve() 的参数
/*
return new Promise(resolve => {
	resolve(undefined);
})
*/

// -----------------------------
// 如果我们在这个返回的 Promise 上继续调用 then 方法,并接收参数的话,可以发现 then 中成功接收到了被 Promise 包装后的参数
const p2 = new Promise((resolve, reject) => {
	resolve()
})
p2.then(() => {
	// 默认会执行这一条
	// return undefined
}).then(data => {
	console.log(data)  // 打印 undefined
	return 24  // 手动 return 一个值
    // 相当于:return new Promise(resolve => {resolve(24)})
}).then((data) => {
	console.log(data) // 打印 24
})

If we want then to return a Promise with a failed status, then we can manually return a Promise and execute the reject() method.

const p3 = new Promise((resolve, reject) => {
	resolve()
})
p3.then(() => {
	// 手动返回一个调用了 reject 的 Promise
	return new Promise((resolve, reject) => {
		reject('失败')
    })
}).then(() => {}, errData => {
	console.log(errData) // 失败
})

Summarize:

  1. Promise is a constructor and requires new to be used. When new Promise(), you need to pass an anonymous callback function as the only parameter of Promise(). This callback function has two parameters, resolve reject, and these two parameters are also functions. When the callback function executes the first resolve function, the Promise It becomes a success state. On the contrary, after the callback function executes reject, the Promise becomes a failure state, and each Promise can only execute either resolve or reject, not both at the same time!
  2. When a Promise is new, there will be a then method. This method receives two anonymous callback functions as parameters by default. The first callback function is automatically called when the Promise is in a successful state. Otherwise, the second callback function is called when the Promise is successful. It is automatically called when the failure state occurs, and these two callback functions can receive parameters. The parameters come from the actual parameters passed when calling resolve or reject!
  3. After the then method is executed, it will return undefined by default (when no return value is specified). ES6 will wrap it into a new successful Promise. This Promise will automatically execute the resolve function. The parameters of this function come from the then method. The return value (if there is no return value, undefined is returned by default). If you need to return a failed Promise, you need to manually specify the return value in then: return new Promise((resolve, reject) => { reject(parameter) }

四、Promise.prototype.catch()

The Promise.prototype.catch() method is an alias for .then(null, rejection) or .then(undefined, rejection) and is used to specify a callback function when an error occurs.

new Promise((resolve, reject) => {
    
    
	reject('失败')
}).then(res => {
    
    
	console.log(res)
}).catch(err => {
    
    
	console.log(err) // 失败
})

// -------------------------------------
// 上面的代码本质上等同于
new Promise((resolve, reject) => {
    
    
	reject('失败')
}).then(res => {
    
    
	console.log(res)
}).then(null, err => {
    
    
	console.log(err) // 失败
})

In Promise, once an error state occurs, the error will not disappear and will be passed down until it encounters a function that can handle the error.

Since catch is a special case of then, catch still returns a Promise object, and we can continue to call then after catch.

new Promise((resolve, reject) => {
    
    
    reject('失败')
}).then(res => {
    
    
	console.log(res)
}).catch(err => {
    
    
	console.log(err) // 失败
    return '测试'
}).then(res => {
    
    
	console.log(res)	// 测试 
})

It is generally recommended that the Promise object be followed by one or more catch methods, so that errors that occur within the Promise can be handled!

五、Promise.prototype.finally()

The Promise.prototype.finally() method is used to specify operations that will be performed regardless of the final state of the Promise object. This method is standard introduced in ES2018.

The callback function of the finally method does not accept any parameters, which means there is no way to know whether the previous Promise status is fulfilled or rejected. This shows that the operations in the finally method should be state-independent and not dependent on the execution result of the Promise.

finally is essentially a special case of the then method.

// 不管 promise 最后的状态,在执行完 then 或 catch 指定的回调函数以后,都会执行 finally 方法指定的回调函数。
promise
.then(result => {
    
    ···})
.catch(error => {
    
    ···})
.finally(() => {
    
    ···})
promise
.finally(() => {
    
    
	// 语句
})

// 等同于
promise
.then(
	result => {
    
    
		// 语句
		return result
	},
	error => {
    
    
		// 语句
		throw error
	}
)

finally: Mainly used to handle some required operations, such as closing the database connection after operating the database (regardless of success or failure).

server.listen(port)
	.then(function () {
    
    
		// ...
	})
	.finally(server.stop)

6. Promise.all()

The Promise.all() method is used to wrap multiple Promise instances into a new Promise instance.

The Promise.all() method accepts an array as a parameter. p1, p2, and p3 are all Promise instances. If not, the Promise.resolve method will be called first to convert the parameters into Promise instances for further processing. In addition, the parameter of the Promise.all() method does not need to be an array, but it must have an Iterator interface, and each member returned is a Promise instance.

const p = Promise.all([p1, p2, p3])

The state of p is determined by p1, p2, and p3, and is divided into two situations.

(1) Only when the status of p1, p2, and p3 all become fulfilled, the status of p will become fulfilled. At this time, the return values ​​of p1, p2, and p3 form an array and are passed to the callback function of p.

(2) As long as one of p1, p2, and p3 is rejected, the status of p becomes rejected. At this time, the return value of the first rejected instance will be passed to the callback function of p.

const p1 = new Promise((resolve,reject)=>{
    
    
	resolve('请求成功')
})
const p2 = new Promise((resolve,reject)=>{
    
    
	resolve('上传成功')
})
const p3 = Promise.reject('error')
 
Promise.all([p1, p2]).then(data => {
    
    
	console.log(data) // data为一个数组  ['请求成功', '上传成功']
}).catch(err => {
    
    
	console.log(err)
})
 
Promise.all([p1, p2, p3]).then(data => {
    
    
	console.log(data)
}).catch(err => {
    
    
	console.log(err) // 失败 打印结果为 'error'
})

Seven, Promise.race()

The Promise.race() method wraps multiple Promise instances into a new Promise instance.

The parameters of the Promise.race() method are the same as the Promise.all() method. If it is not a Promise instance, the Promise.resolve() method will be called first to convert the parameters into a Promise instance before further processing.

const p = Promise.race([p1, p2, p3])

The state of p is determined by p1, p2, p3.

One instance among p1, p2, and p3 changes the state first, and the state of p changes accordingly. The return value of the Promise instance that changed first is passed to the callback function of p.

const p = Promise.race([
	fetch('/resource-that-may-take-a-while'),
	new Promise(function (resolve, reject) {
    
    
		setTimeout(() => reject(new Error('request timeout')), 5000)
	})
])

p
.then(console.log)
.catch(console.error)

In the above code, if the fetch method cannot return the result within 5 seconds, the status of the variable p will change to rejected, thus triggering the callback function specified by the catch method.

八、Promise.allSettled()

Promise.allSettled() method is used to determine whether a set of asynchronous operations has ended (regardless of success or failure).

The Promise.allSettled() method accepts an array as a parameter, each member of the array is a Promise object, and returns a new Promise object. Only when all Promise objects in the parameter array have changed state (whether fulfilled or rejected) will the returned Promise object change state.

const promises = [
	fetch('/api-1'),
	fetch('/api-2'),
	fetch('/api-3')
]

await Promise.allSettled(promises)
removeLoadingIndicator()

In the above example, the array promises contains three requests. RemoveLoadingIndicator() will not be executed until all three requests are completed (regardless of whether the request succeeds or fails).

For the new Promise instance returned by this method, once the status changes, the status is always fulfilled and will not become rejected. After the status becomes fulfilled, its callback function will receive an array as a parameter, and each member of the array corresponds to each Promise object in the previous array.

const resolved = Promise.resolve(42)
const rejected = Promise.reject(-1)

const allSettledPromise = Promise.allSettled([resolved, rejected])

allSettledPromise.then(function (results) {
    
    
	console.log(results)
})
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

In the above code, the return value of Promise.allSettled() is allSettledPromise, and the status can only become fulfilled. The parameter received by its callback function is the array results. Each member of the array is an object, corresponding to the two Promise objects in the array passed to Promise.allSettled().

Each member of results is an object. The format of the object is fixed and corresponds to the results of asynchronous operations.

// 异步操作成功时
{
    
     status: 'fulfilled', value: value }

// 异步操作失败时
{
    
     status: 'rejected', reason: reason }

The value of the status attribute of the member object can only be the string fulfilled or the string rejected, which is used to distinguish whether the asynchronous operation is successful or failed. If it is successful (fulfilled), the object will have a value attribute. If it is failed (rejected), it will have a reason attribute, corresponding to the return value of the previous asynchronous operation in the two states.

The following are examples of usage of return values.

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ]
const results = await Promise.allSettled(promises)

// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled')

// 过滤出失败的请求,并输出原因
const errors = results
	.filter(p => p.status === 'rejected')
	.map(p => p.reason)

九、Promise.any()

ES2021 introduced the Promise.any() method. This method accepts a set of Promise instances as parameters, wraps them into a new Promise instance and returns it.

As long as one parameter instance becomes fulfilled, the packaging instance will become fulfilled; if all parameter instances become rejected, the packaging instance will become rejected.

Promise.any([
	fetch('https://v8.dev/').then(() => 'home'),
	fetch('https://v8.dev/blog').then(() => 'blog'),
	fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => {
    
      // 只要有一个 fetch() 请求成功
	console.log(first)
}).catch((error) => {
    
     // 所有三个 fetch() 全部请求失败
	console.log(error)
})

Promise.any() is very similar to the Promise.race() method. The only difference is that Promise.any() will not end when a Promise becomes rejected. It must wait until all parameter Promise becomes rejected. .

The following is an example of using Promise() with the await command.

const promises = [
	fetch('/endpoint-a').then(() => 'a'),
	fetch('/endpoint-b').then(() => 'b'),
	fetch('/endpoint-c').then(() => 'c'),
]

try {
    
    
	const success = await Promise.any(promises)
	console.log(success)
} catch (error) {
    
    
	console.log(error)
}

In the above code, the parameter array of the Promise.any() method contains three Promise operations. As long as one of them becomes fulfilled, the Promise object returned by Promise.any() becomes fulfilled. If all three operations become rejected, the await command throws an error.

The error thrown by Promise.any() is an AggregateError instance. The errors property of this AggregateError instance object is an array that contains the errors of all members.

Below is an example.

var resolved = Promise.resolve(42)
var rejected = Promise.reject(-1)
var alsoRejected = Promise.reject(Infinity)

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
    
    
	console.log(result); // 42
})

Promise.any([rejected, alsoRejected]).catch(function (results) {
    
    
	console.log(results instanceof AggregateError) // true
	console.log(results.errors) // [-1, Infinity]
})

十、Promise.resolve()

Promise.resolve() method, used to convert existing objects into Promise objects.

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Parameters of Promise.resolve() method:

(1) Parameters are general parameters

If the parameter is a primitive value, or an object without a then() method, the Promise.resolve() method returns a new Promise object with the status resolved.

const p = Promise.resolve('Hello')

p.then(function (s) {
    
    
	console.log(s)
})
// Hello

(2) The parameter is a Promise instance

If the argument is a Promise instance, Promise.resolve will return the instance unchanged without any modifications.

(3) Without any parameters

The Promise.resolve() method allows calling without parameters and directly returns a Promise object in resolved state.

Therefore, if you want to get a Promise object, the more convenient way is to directly call the Promise.resolve() method.

const p = Promise.resolve()

p.then(function () {
    
    
	// ...
})

(4) The parameter is a thenable object (just understand it)

When receiving an object with a then method, Promise.resolve() will call the then method directly.

A thenable object refers to an object with a then method.

const thenable = {
    
    
	then() {
    
    
		console.log('then')
	}
}
Promise.resolve(thenable).then(
	res => console.log('res ' + res),
	err => console.log('err ' + err)
)

console.log(Promise.resolve(thenable))
// Promise { <pending> }
// then

// 为什么不会执行 then 中的两个回调函数呢?
// 可见,当接收一个含 then 方法的对象时,默认返回一个 Promise 并且是等待状态的,没有状态的变化,那么就不可能会执行 then 的回调函数



// 如果我们要改变这个返回的 Promise 对象的状态,并让 then 的回调对应处理的话,ES6 规定了以下写法:
const thenable02 = {
    
    
	then(resolve, reject) {
    
    
		console.log('then')
		resolve('then')
	}
}
Promise.resolve(thenable02).then(
    res => console.log('res ' + res),
	err => console.log('err ' + err)
)
// then
// res then

11. Promise.reject()

The Promise.reject(reason) method also returns a new Promise instance with a status of rejected.

const p = Promise.reject('出错了')
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
    
    
	console.log(s)
})
// 出错了

The above code generates an instance p of the Promise object, the status is rejected, and the callback function will be executed immediately.

The parameters of the Promise.reject() method will remain unchanged as the reason for rejection and become the parameters of subsequent methods.

Promise.reject('出错了')
.catch(e => {
    
    
	console.log(e === '出错了')
})
// true

12. Promise.try()

In actual development, we often encounter a situation: we don't know or don't want to distinguish whether function f is a synchronous function or an asynchronous operation, but we want to use Promise to handle it. Because in this way, regardless of whether f contains asynchronous operations, the then method can be used to specify the next process, and the catch method can be used to handle errors thrown by f. Generally, the following writing method will be used.

Promise.resolve().then(f)

One disadvantage of the above writing method is that if f is a synchronous function, it will be executed at the end of this round of event loop.

// 函数 f 是同步的,但是用 Promise 包装了以后,就变成异步执行了。
const f = () => console.log('now')
Promise.resolve().then(f)
console.log('next')
// next
// now

The Promise.try method allows synchronous functions to be executed synchronously and asynchronous functions to be executed asynchronously, and allows them to have a unified API.

const f = () => console.log('now')
Promise.try(f)
console.log('next')
// now
// next

database.users.get() returns a Promise object. If an asynchronous error is thrown, it can be captured using the catch method. However, database.users.get() may also throw synchronization errors (such as database connection errors, depending on the implementation method), and then you have to use try...catch to catch them.

In fact, Promise.try simulates the try code block, just like promise.catch simulates the catch code block.

// 统一用 promise.catch() 捕获所有同步和异步的错误。
Promise.try(() => database.users.get({
    
    id: userId}))
	.then(...)
	.catch(...)

// ----------------------
// 等同于

try {
    
    
	database.users.get({
    
    id: userId})
	.then(...)
	.catch(...)
} catch (e) {
    
    
	// ...
}

13. Application of Promise

Asynchronous loading: also called preloading of images. Using js code to load images in advance, users can obtain them directly from the local cache when needed, but this will increase the pressure on the server front-end. This can improve the user experience, because when a large image is loaded synchronously, the image will be displayed layer by layer, but after preloading, the entire image will be displayed directly.

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Promise 的应用</title>
    <style>
        #img {
      
      
            width: 24%;
            padding: 24px;
        }
    </style>
</head>
<body>
<!-- 一般加载图片方式 -->
<!-- <img src="https://scpic.chinaz.net/files/pic/pic9/202009/apic27858.jpg" /> -->
<img src="" alt="" id="img">

<script>
	// 异步加载图片函数(参数:图片路径)
    const loadImgAsync = url => {
      
      
        return new Promise((resolve, reject) => {
      
      
            const img = new Image() // 创建一个图片对象

            img.onload = () => {
      
       // 图片成功加载触发事件
                resolve(img)
            }
           
            img.onerror = () => {
      
        // 图片加载失败触发事件
                reject(new Error(`Could not load image at ${ 
        url}`))
            }

            // 这个放在 onload 与 onerror 之后
            // 一但给 img.src 赋值,那么便立马开始发送请求加载图片(在后台加载,页面上不会显示)
            // 注意:这里的 src 是 img 对象的属性,与 html 中 img 的 src 无关
            img.src = url
        })
    }

    const imgDOM = document.getElementById('img')
    loadImgAsync('https://scpic.chinaz.net/files/pic/pic9/202009/apic27858.jpg')
    	.then(img => {
      
      
            // 如果加载成功,那么把后台缓存的图片显示到页面上
            imgDOM.src = img.src
        })
        .catch(err => {
      
      
            console.log(err)
        })
</script>
</body>
</html>

Guess you like

Origin blog.csdn.net/weixin_45559449/article/details/135248693